/*  Copyright (C) 2006 yopyop
    yopyop156@ifrance.com
    yopyop156.ifrance.com

    This file is part of DeSmuME

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

    DeSmuME 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 DeSmuME; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

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

#include "NDSSystem.h"
#include "MMU.h"
#include "cflash.h"

#include "MMU.h"
#include "cflash.h"
#include "ROMReader.h"

/* the count of bytes copied from the firmware into memory */
#define NDS_FW_USER_SETTINGS_MEM_BYTE_COUNT 0x70

NDSSystem nds;

static u32
calc_CRC16( u32 start, const u8 *data, int count) {
  int i,j;
  u32 crc = start & 0xffff;
  static u16 val[] = { 0xC0C1,0xC181,0xC301,0xC601,0xCC01,0xD801,0xF001,0xA001 };
  for(i = 0; i < count; i++)
  {
    crc = crc ^ data[i];

    for(j = 0; j < 8; j++) {
      int do_bit = 0;

      if ( crc & 0x1)
        do_bit = 1;

      crc = crc >> 1;

      if ( do_bit) {
        crc = crc ^ (val[j] << (7-j));
      }
    }
  }
  return crc;
}

static int
copy_firmware_user_data( u8 *dest_buffer, const u8 *fw_data) {
  /*
   * Determine which of the two user settings in the firmware is the current
   * and valid one and then copy this into the destination buffer.
   *
   * The current setting will have a greater count.
   * Settings are only valid if its CRC16 is correct.
   */
  int user1_valid = 0;
  int user2_valid = 0;
  u32 user_settings_offset;
  u32 fw_crc;
  u32 crc;
  int copy_good = 0;

  user_settings_offset = fw_data[0x20];
  user_settings_offset |= fw_data[0x21] << 8;
  user_settings_offset <<= 3;

  if ( user_settings_offset <= 0x3FE00) {
    s32 copy_settings_offset = -1;

    crc = calc_CRC16( 0xffff, &fw_data[user_settings_offset],
                      NDS_FW_USER_SETTINGS_MEM_BYTE_COUNT);
    fw_crc = fw_data[user_settings_offset + 0x72];
    fw_crc |= fw_data[user_settings_offset + 0x73] << 8;
    if ( crc == fw_crc) {
      user1_valid = 1;
    }

    crc = calc_CRC16( 0xffff, &fw_data[user_settings_offset + 0x100],
                      NDS_FW_USER_SETTINGS_MEM_BYTE_COUNT);
    fw_crc = fw_data[user_settings_offset + 0x100 + 0x72];
    fw_crc |= fw_data[user_settings_offset + 0x100 + 0x73] << 8;
    if ( crc == fw_crc) {
      user2_valid = 1;
    }

    if ( user1_valid) {
      if ( user2_valid) {
        u16 count1, count2;

        count1 = fw_data[user_settings_offset + 0x70];
        count1 |= fw_data[user_settings_offset + 0x71] << 8;

        count2 = fw_data[user_settings_offset + 0x100 + 0x70];
        count2 |= fw_data[user_settings_offset + 0x100 + 0x71] << 8;

        if ( count2 > count1) {
          copy_settings_offset = user_settings_offset + 0x100;
        }
        else {
          copy_settings_offset = user_settings_offset;
        }
      }
      else {
        copy_settings_offset = user_settings_offset;
      }
    }
    else if ( user2_valid) {
      /* copy the second user settings */
      copy_settings_offset = user_settings_offset + 0x100;
    }

    if ( copy_settings_offset > 0) {
      memcpy( dest_buffer, &fw_data[copy_settings_offset],
              NDS_FW_USER_SETTINGS_MEM_BYTE_COUNT);
      copy_good = 1;
    }
  }

  return copy_good;
}


int NDS_Init( struct armcpu_memory_iface *arm9_mem_if,
              struct armcpu_ctrl_iface **arm9_ctrl_iface,
              struct armcpu_memory_iface *arm7_mem_if,
              struct armcpu_ctrl_iface **arm7_ctrl_iface) {
     nds.ARM9Cycle = 0;
     nds.ARM7Cycle = 0;
     nds.cycles = 0;
     MMU_Init();
     nds.nextHBlank = 3168;
     nds.VCount = 0;
     nds.lignerendu = FALSE;

     if (Screen_Init(GFXCORE_DUMMY) != 0)
        return -1;
     
     armcpu_new(&NDS_ARM7,1, arm7_mem_if, arm7_ctrl_iface);
     armcpu_new(&NDS_ARM9,0, arm9_mem_if, arm9_ctrl_iface);

     if (SPU_Init(SNDCORE_DUMMY, 735) != 0)
        return -1;

#ifdef EXPERIMENTAL_WIFI
	 WIFI_Init(&wifiMac) ;
#endif

     return 0;
}

void NDS_DeInit(void) {
     if(MMU.CART_ROM != MMU.UNUSED_RAM)
        NDS_FreeROM();

     nds.nextHBlank = 3168;
     SPU_DeInit();
     Screen_DeInit();
     MMU_DeInit();
}

BOOL NDS_SetROM(u8 * rom, u32 mask)
{
     MMU_setRom(rom, mask);

     return TRUE;
} 

NDS_header * NDS_getROMHeader(void)
{
	NDS_header * header = malloc(sizeof(NDS_header));

	memcpy(header->gameTile, MMU.CART_ROM, 12);
	memcpy(header->gameCode, MMU.CART_ROM + 12, 4);
	header->makerCode = T1ReadWord(MMU.CART_ROM, 16);
	header->unitCode = MMU.CART_ROM[18];
	header->deviceCode = MMU.CART_ROM[19];
	header->cardSize = MMU.CART_ROM[20];
	memcpy(header->cardInfo, MMU.CART_ROM + 21, 8);
	header->flags = MMU.CART_ROM[29];
	header->ARM9src = T1ReadLong(MMU.CART_ROM, 32);
	header->ARM9exe = T1ReadLong(MMU.CART_ROM, 36);
	header->ARM9cpy = T1ReadLong(MMU.CART_ROM, 40);
	header->ARM9binSize = T1ReadLong(MMU.CART_ROM, 44);
	header->ARM7src = T1ReadLong(MMU.CART_ROM, 48);
	header->ARM7exe = T1ReadLong(MMU.CART_ROM, 52);
	header->ARM7cpy = T1ReadLong(MMU.CART_ROM, 56);
	header->ARM7binSize = T1ReadLong(MMU.CART_ROM, 60);
	header->FNameTblOff = T1ReadLong(MMU.CART_ROM, 64);
	header->FNameTblSize = T1ReadLong(MMU.CART_ROM, 68);
	header->FATOff = T1ReadLong(MMU.CART_ROM, 72);
	header->FATSize = T1ReadLong(MMU.CART_ROM, 76);
	header->ARM9OverlayOff = T1ReadLong(MMU.CART_ROM, 80);
	header->ARM9OverlaySize = T1ReadLong(MMU.CART_ROM, 84);
	header->ARM7OverlayOff = T1ReadLong(MMU.CART_ROM, 88);
	header->ARM7OverlaySize = T1ReadLong(MMU.CART_ROM, 92);
	header->unknown2a = T1ReadLong(MMU.CART_ROM, 96);
	header->unknown2b = T1ReadLong(MMU.CART_ROM, 100);
	header->IconOff = T1ReadLong(MMU.CART_ROM, 104);
	header->CRC16 = T1ReadWord(MMU.CART_ROM, 108);
	header->ROMtimeout = T1ReadWord(MMU.CART_ROM, 110);
	header->ARM9unk = T1ReadLong(MMU.CART_ROM, 112);
	header->ARM7unk = T1ReadLong(MMU.CART_ROM, 116);
	memcpy(header->unknown3c, MMU.CART_ROM + 120, 8);
	header->ROMSize = T1ReadLong(MMU.CART_ROM, 128);
	header->HeaderSize = T1ReadLong(MMU.CART_ROM, 132);
	memcpy(header->unknown5, MMU.CART_ROM + 136, 56);
	memcpy(header->logo, MMU.CART_ROM + 192, 156);
	header->logoCRC16 = T1ReadWord(MMU.CART_ROM, 348);
	header->headerCRC16 = T1ReadWord(MMU.CART_ROM, 350);
	memcpy(header->reserved, MMU.CART_ROM + 352, 160);

	return header;

     //return (NDS_header *)MMU.CART_ROM;
} 



void NDS_FreeROM(void)
{
   if (MMU.CART_ROM != MMU.UNUSED_RAM)
      free(MMU.CART_ROM);
   MMU_unsetRom();
   if (MMU.bupmem.fp)
      fclose(MMU.bupmem.fp);
   MMU.bupmem.fp = NULL;
}

                         

void NDS_Reset( void)
{
   BOOL oldexecute=execute;
   int i;
   u32 src;
   u32 dst;
   NDS_header * header = NDS_getROMHeader();

	if (!header) return ;

   execute = FALSE;

   MMU_clearMem();

   src = header->ARM9src;
   dst = header->ARM9cpy;

   for(i = 0; i < (header->ARM9binSize>>2); ++i)
   {
      MMU_writeWord(0, dst, T1ReadLong(MMU.CART_ROM, src));
      dst += 4;
      src += 4;
   }

   src = header->ARM7src;
   dst = header->ARM7cpy;
     
   for(i = 0; i < (header->ARM7binSize>>2); ++i)
   {
      MMU_writeWord(1, dst, T1ReadLong(MMU.CART_ROM, src));
      dst += 4;
      src += 4;
   }

   armcpu_init(&NDS_ARM7, header->ARM7exe);
   armcpu_init(&NDS_ARM9, header->ARM9exe);
     
   nds.ARM9Cycle = 0;
   nds.ARM7Cycle = 0;
   nds.cycles = 0;
   memset(nds.timerCycle, 0, sizeof(s32) * 2 * 4);
   memset(nds.timerOver, 0, sizeof(BOOL) * 2 * 4);
   nds.nextHBlank = 3168;
   nds.VCount = 0;
   nds.old = 0;
   nds.diff = 0;
   nds.lignerendu = FALSE;
   nds.touchX = nds.touchY = 0;

   MMU_writeHWord(0, 0x04000130, 0x3FF);
   MMU_writeHWord(1, 0x04000130, 0x3FF);
   MMU_writeByte(1, 0x04000136, 0x43);

   /*
    * Setup a copy of the firmware user settings in memory.
    * (this is what the DS firmware would do).
    */
   {
     u8 temp_buffer[NDS_FW_USER_SETTINGS_MEM_BYTE_COUNT];
     int fw_index;

     if ( copy_firmware_user_data( temp_buffer, MMU.fw.data)) {
       for ( fw_index = 0; fw_index < NDS_FW_USER_SETTINGS_MEM_BYTE_COUNT; fw_index++) {
         MMU_writeByte( 0, 0x027FFC80 + fw_index, temp_buffer[fw_index]);
       }
     }
   }
          
   MMU_writeWord(0, 0x027FFE40, header->FNameTblOff);
   MMU_writeWord(0, 0x027FFE44, header->FNameTblSize);
   MMU_writeWord(0, 0x027FFE48, header->FATOff);
   MMU_writeWord(0, 0x027FFE4C, header->FATSize);
     
   MMU_writeWord(0, 0x027FFE50, header->ARM9OverlayOff);
   MMU_writeWord(0, 0x027FFE54, header->ARM9OverlaySize);
   MMU_writeWord(0, 0x027FFE58, header->ARM7OverlayOff);
   MMU_writeWord(0, 0x027FFE5C, header->ARM7OverlaySize);
     
   MMU_writeWord(0, 0x027FFE60, header->unknown2a);
   MMU_writeWord(0, 0x027FFE64, header->unknown2b);  //merci EACKiX

   MMU_writeWord(0, 0x027FFE70, header->ARM9unk);
   MMU_writeWord(0, 0x027FFE74, header->ARM7unk);

   MMU_writeWord(0, 0x027FFF9C, 0x027FFF90); // ?????? besoin d'avoir la vrai valeur sur ds
     
   MainScreen.offset = 192;
   SubScreen.offset = 0;
     
   //MMU_writeWord(0, 0x02007FFC, 0xE92D4030);

     //ARM7 BIOS IRQ HANDLER
     MMU_writeWord(1, 0x00, 0xE25EF002);
     MMU_writeWord(1, 0x04, 0xEAFFFFFE);
     MMU_writeWord(1, 0x18, 0xEA000000);
     MMU_writeWord(1, 0x20, 0xE92D500F);
     MMU_writeWord(1, 0x24, 0xE3A00301);
     MMU_writeWord(1, 0x28, 0xE28FE000);
     MMU_writeWord(1, 0x2C, 0xE510F004);
     MMU_writeWord(1, 0x30, 0xE8BD500F);
     MMU_writeWord(1, 0x34, 0xE25EF004);
    
     //ARM9 BIOS IRQ HANDLER
     MMU_writeWord(0, 0xFFFF0018, 0xEA000000);
     MMU_writeWord(0, 0xFFFF0020, 0xE92D500F);
     MMU_writeWord(0, 0xFFFF0024, 0xEE190F11);
     MMU_writeWord(0, 0xFFFF0028, 0xE1A00620);
     MMU_writeWord(0, 0xFFFF002C, 0xE1A00600);
     MMU_writeWord(0, 0xFFFF0030, 0xE2800C40);
     MMU_writeWord(0, 0xFFFF0034, 0xE28FE000);
     MMU_writeWord(0, 0xFFFF0038, 0xE510F004);
     MMU_writeWord(0, 0xFFFF003C, 0xE8BD500F);
     MMU_writeWord(0, 0xFFFF0040, 0xE25EF004);
        
     MMU_writeWord(0, 0x0000004, 0xE3A0010E);
     MMU_writeWord(0, 0x0000008, 0xE3A01020);
//     MMU_writeWord(0, 0x000000C, 0xE1B02110);
     MMU_writeWord(0, 0x000000C, 0xE1B02040);
     MMU_writeWord(0, 0x0000010, 0xE3B02020);
//     MMU_writeWord(0, 0x0000010, 0xE2100202);

   free(header);

   GPU_Reset(MainScreen.gpu, 0);
   GPU_Reset(SubScreen.gpu, 1);
   SPU_Reset();

   execute = oldexecute;
}

static void dma_check(void)
{
	if((MMU.DMAing[0][0])&&(MMU.DMACycle[0][0]<=nds.cycles))
	{
		T1WriteLong(ARM9Mem.ARM9_REG, 0xB8 + (0xC*0), T1ReadLong(ARM9Mem.ARM9_REG, 0xB8 + (0xC*0)) & 0x7FFFFFFF);
		if((MMU.DMACrt[0][0])&(1<<30)) NDS_makeARM9Int(8);
		MMU.DMAing[0][0] = FALSE;
	}

	if((MMU.DMAing[0][1])&&(MMU.DMACycle[0][1]<=nds.cycles))
	{
		T1WriteLong(ARM9Mem.ARM9_REG, 0xB8 + (0xC*1), T1ReadLong(ARM9Mem.ARM9_REG, 0xB8 + (0xC*1)) & 0x7FFFFFFF);
		if((MMU.DMACrt[0][1])&(1<<30)) NDS_makeARM9Int(9);
		MMU.DMAing[0][1] = FALSE;
	}

	if((MMU.DMAing[0][2])&&(MMU.DMACycle[0][2]<=nds.cycles))
	{
		T1WriteLong(ARM9Mem.ARM9_REG, 0xB8 + (0xC*2), T1ReadLong(ARM9Mem.ARM9_REG, 0xB8 + (0xC*2)) & 0x7FFFFFFF);
		if((MMU.DMACrt[0][2])&(1<<30)) NDS_makeARM9Int(10);
		MMU.DMAing[0][2] = FALSE;
	}

	if((MMU.DMAing[0][3])&&(MMU.DMACycle[0][3]<=nds.cycles))
	{
		T1WriteLong(ARM9Mem.ARM9_REG, 0xB8 + (0xC*3), T1ReadLong(ARM9Mem.ARM9_REG, 0xB8 + (0xC*3)) & 0x7FFFFFFF);
		if((MMU.DMACrt[0][3])&(1<<30)) NDS_makeARM9Int(11);
		MMU.DMAing[0][3] = FALSE;
	}

	if((MMU.DMAing[1][0])&&(MMU.DMACycle[1][0]<=nds.cycles))
	{
		T1WriteLong(MMU.ARM7_REG, 0xB8 + (0xC*0), T1ReadLong(MMU.ARM7_REG, 0xB8 + (0xC*0)) & 0x7FFFFFFF);
		if((MMU.DMACrt[1][0])&(1<<30)) NDS_makeARM7Int(8);
		MMU.DMAing[1][0] = FALSE;
	}

	if((MMU.DMAing[1][1])&&(MMU.DMACycle[1][1]<=nds.cycles))
	{
		T1WriteLong(MMU.ARM7_REG, 0xB8 + (0xC*1), T1ReadLong(MMU.ARM7_REG, 0xB8 + (0xC*1)) & 0x7FFFFFFF);
		if((MMU.DMACrt[1][1])&(1<<30)) NDS_makeARM7Int(9);
		MMU.DMAing[1][1] = FALSE;
	}

	if((MMU.DMAing[1][2])&&(MMU.DMACycle[1][2]<=nds.cycles))
	{
		T1WriteLong(MMU.ARM7_REG, 0xB8 + (0xC*2), T1ReadLong(MMU.ARM7_REG, 0xB8 + (0xC*2)) & 0x7FFFFFFF);
		if((MMU.DMACrt[1][2])&(1<<30)) NDS_makeARM7Int(10);
		MMU.DMAing[1][2] = FALSE;
	}

	if((MMU.DMAing[1][3])&&(MMU.DMACycle[1][3]<=nds.cycles))
	{
		T1WriteLong(MMU.ARM7_REG, 0xB8 + (0xC*3), T1ReadLong(MMU.ARM7_REG, 0xB8 + (0xC*3)) & 0x7FFFFFFF);
		if((MMU.DMACrt[1][3])&(1<<30)) NDS_makeARM7Int(11);
		MMU.DMAing[1][3] = FALSE;
	}

	if((MMU.reg_IF[0]&MMU.reg_IE[0]) && (MMU.reg_IME[0]))
	//if(NDS_ARM9.irqExeption())
		if ( armcpu_flagIrq( &NDS_ARM9)) {
			nds.ARM9Cycle = nds.cycles;
		}
	/*
	if(armcpu_irqExeption(&NDS_ARM9))
	{
	nds.ARM9Cycle = nds.cycles;
	}
	*/

	if((MMU.reg_IF[1]&MMU.reg_IE[1]) && (MMU.reg_IME[1]))
		if ( armcpu_flagIrq( &NDS_ARM7)) {
			nds.ARM7Cycle = nds.cycles;
		}
	/*
	if (armcpu_irqExeption(&NDS_ARM7))
	nds.ARM7Cycle = nds.cycles;
	*/
}

static void timer_check(void)
{
	/* assume the timers have not expired */
	nds.timerOver[0][0] = 0;
	nds.timerOver[0][1] = 0;
	nds.timerOver[0][2] = 0;
	nds.timerOver[0][3] = 0;
	nds.timerOver[1][0] = 0;
	nds.timerOver[1][1] = 0;
	nds.timerOver[1][2] = 0;
	nds.timerOver[1][3] = 0;
	if(MMU.timerON[0][0])
	{
		if(MMU.timerRUN[0][0])
		{
			switch(MMU.timerMODE[0][0])
			{
			case 0xFFFF :
				break;
			default :
				{
					nds.diff = (nds.cycles - nds.timerCycle[0][0])>>MMU.timerMODE[0][0];
					nds.old = MMU.timer[0][0];
					MMU.timer[0][0] += nds.diff;
					nds.timerCycle[0][0] += (nds.diff << MMU.timerMODE[0][0]);
					nds.timerOver[0][0] = nds.old>MMU.timer[0][0];
					if(nds.timerOver[0][0])
					{
						if(T1ReadWord(ARM9Mem.ARM9_REG, 0x102) & 0x40)
							NDS_makeARM9Int(3);
						MMU.timer[0][0] = MMU.timerReload[0][0];
					}
				}
				break;
			}
		}
		else
		{
			MMU.timerRUN[0][0] = TRUE;
			nds.timerCycle[0][0] = nds.cycles;
		}
	}
	if(MMU.timerON[0][1])
	{
		if(MMU.timerRUN[0][1])
		{
			switch(MMU.timerMODE[0][1])
			{
			case 0xFFFF :
				if(nds.timerOver[0][0])
				{
					++(MMU.timer[0][1]);
					nds.timerOver[0][1] = !MMU.timer[0][1];
					if (nds.timerOver[0][1])
					{
						if(T1ReadWord(ARM9Mem.ARM9_REG, 0x106) & 0x40)
							NDS_makeARM9Int(4);
						MMU.timer[0][1] = MMU.timerReload[0][1];
					}
				}
				break;
			default :
				{
					nds.diff = (nds.cycles - nds.timerCycle[0][1])>>MMU.timerMODE[0][1];
					nds.old = MMU.timer[0][1];
					MMU.timer[0][1] += nds.diff;
					nds.timerCycle[0][1] += nds.diff << MMU.timerMODE[0][1];
					nds.timerOver[0][1] = nds.old>MMU.timer[0][1];
					if(nds.timerOver[0][1])
					{
						if(T1ReadWord(ARM9Mem.ARM9_REG, 0x106) & 0x40)
							NDS_makeARM9Int(4);
						MMU.timer[0][1] = MMU.timerReload[0][1];
					}
				}
				break;
			}
		}
		else
		{
			MMU.timerRUN[0][1] = TRUE;
			nds.timerCycle[0][1] = nds.cycles;
		}
	}
	if(MMU.timerON[0][2])
	{
		if(MMU.timerRUN[0][2])
		{
			switch(MMU.timerMODE[0][2])
			{
			case 0xFFFF :
				if(nds.timerOver[0][1])
				{
					++(MMU.timer[0][2]);
					nds.timerOver[0][2] = !MMU.timer[0][2];
					if (nds.timerOver[0][2])
					{
						if(T1ReadWord(ARM9Mem.ARM9_REG, 0x10A) & 0x40)
							NDS_makeARM9Int(5);
						MMU.timer[0][2] = MMU.timerReload[0][2];
					}
				}
				break;
			default :
				{
					nds.diff = (nds.cycles - nds.timerCycle[0][2])>>MMU.timerMODE[0][2];
					nds.old = MMU.timer[0][2];
					MMU.timer[0][2] += nds.diff;
					nds.timerCycle[0][2] += nds.diff << MMU.timerMODE[0][2];
					nds.timerOver[0][2] = nds.old>MMU.timer[0][2];
					if(nds.timerOver[0][2])
					{
						if(T1ReadWord(ARM9Mem.ARM9_REG, 0x10A) & 0x40)
							NDS_makeARM9Int(5);
						MMU.timer[0][2] = MMU.timerReload[0][2];
					}
				}
				break;
			}
		}
		else
		{
			MMU.timerRUN[0][2] = TRUE;
			nds.timerCycle[0][2] = nds.cycles;
		}
	}

	if(MMU.timerON[0][3])
	{
		if(MMU.timerRUN[0][3])
		{
			switch(MMU.timerMODE[0][3])
			{
			case 0xFFFF :
				if(nds.timerOver[0][2])
				{
					++(MMU.timer[0][3]);
					nds.timerOver[0][3] = !MMU.timer[0][3];
					if (nds.timerOver[0][3])
					{
						if(T1ReadWord(ARM9Mem.ARM9_REG, 0x10E) & 0x40)
							NDS_makeARM9Int(6);
						MMU.timer[0][3] = MMU.timerReload[0][3];
					}
				}
				break;
			default :
				{
					nds.diff = (nds.cycles - nds.timerCycle[0][3])>>MMU.timerMODE[0][3];
					nds.old = MMU.timer[0][3];
					MMU.timer[0][3] += nds.diff;
					nds.timerCycle[0][3] += nds.diff << MMU.timerMODE[0][3];
					nds.timerOver[0][3] = nds.old>MMU.timer[0][3];
					if(nds.timerOver[0][3])
					{
						if(T1ReadWord(ARM9Mem.ARM9_REG, 0x10E) & 0x40)
							NDS_makeARM9Int(6);
						MMU.timer[0][3] = MMU.timerReload[0][3];
					}
				}
				break;
			}
		}
		else
		{
			MMU.timerRUN[0][3] = TRUE;
			nds.timerCycle[0][3] = nds.cycles;
		}
	}

	if(MMU.timerON[1][0])
	{
		if(MMU.timerRUN[1][0])
		{
			switch(MMU.timerMODE[1][0])
			{
			case 0xFFFF :
				break;
			default :
				{
					nds.diff = (nds.cycles - nds.timerCycle[1][0])>>MMU.timerMODE[1][0];
					nds.old = MMU.timer[1][0];
					MMU.timer[1][0] += nds.diff;
					nds.timerCycle[1][0] += nds.diff << MMU.timerMODE[1][0];
					nds.timerOver[1][0] = nds.old>MMU.timer[1][0];
					if(nds.timerOver[1][0])
					{
						if(T1ReadWord(MMU.ARM7_REG, 0x102) & 0x40)
							NDS_makeARM7Int(3);
						MMU.timer[1][0] = MMU.timerReload[1][0];
					}
				}
				break;
			}
		}
		else
		{
			MMU.timerRUN[1][0] = TRUE;
			nds.timerCycle[1][0] = nds.cycles;
		}
	}
	if(MMU.timerON[1][1])
	{
		if(MMU.timerRUN[1][1])
		{
			switch(MMU.timerMODE[1][1])
			{
			case 0xFFFF :
				if(nds.timerOver[1][0])
				{
					++(MMU.timer[1][1]);
					nds.timerOver[1][1] = !MMU.timer[1][1];
					if (nds.timerOver[1][1])
					{
						if(T1ReadWord(MMU.ARM7_REG, 0x106) & 0x40)
							NDS_makeARM7Int(4);
						MMU.timer[1][1] = MMU.timerReload[1][1];
					}
				}
				break;
			default :
				{
					nds.diff = (nds.cycles - nds.timerCycle[1][1])>>MMU.timerMODE[1][1];
					nds.old = MMU.timer[1][1];
					MMU.timer[1][1] += nds.diff;
					nds.timerCycle[1][1] += nds.diff << MMU.timerMODE[1][1];
					nds.timerOver[1][1] = nds.old>MMU.timer[1][1];
					if(nds.timerOver[1][1])
					{
						if(T1ReadWord(MMU.ARM7_REG, 0x106) & 0x40)
							NDS_makeARM7Int(4);
						MMU.timer[1][1] = MMU.timerReload[1][1];
					}
				}
				break;
			}
		}
		else
		{
			MMU.timerRUN[1][1] = TRUE;
			nds.timerCycle[1][1] = nds.cycles;
		}
	}
	if(MMU.timerON[1][2])
	{
		if(MMU.timerRUN[1][2])
		{
			switch(MMU.timerMODE[1][2])
			{
			case 0xFFFF :
				if(nds.timerOver[1][1])
				{
					++(MMU.timer[1][2]);
					nds.timerOver[1][2] = !MMU.timer[1][2];
					if (nds.timerOver[1][2])
					{
						if(T1ReadWord(MMU.ARM7_REG, 0x10A) & 0x40)
							NDS_makeARM7Int(5);
						MMU.timer[1][2] = MMU.timerReload[1][2];
					}
				}
				break;
			default :
				{
					nds.diff = (nds.cycles - nds.timerCycle[1][2])>>MMU.timerMODE[1][2];
					nds.old = MMU.timer[1][2];
					MMU.timer[1][2] += nds.diff;
					nds.timerCycle[1][2] += nds.diff << MMU.timerMODE[1][2];
					nds.timerOver[1][2] = nds.old>MMU.timer[1][2];
					if(nds.timerOver[1][2])
					{
						if(T1ReadWord(MMU.ARM7_REG, 0x10A) & 0x40)
							NDS_makeARM7Int(5);
						MMU.timer[1][2] = MMU.timerReload[1][2];
					}
				}
				break;
			}
		}
		else
		{
			MMU.timerRUN[1][2] = TRUE;
			nds.timerCycle[1][2] = nds.cycles;
		}
	}
	if(MMU.timerON[1][3])
	{
		if(MMU.timerRUN[1][3])
		{
			switch(MMU.timerMODE[1][3])
			{
			case 0xFFFF :
				if(nds.timerOver[1][2])
				{
					++(MMU.timer[1][3]);
					nds.timerOver[1][3] = !MMU.timer[1][3];
					if (nds.timerOver[1][3])
					{
						if(T1ReadWord(MMU.ARM7_REG, 0x10E) & 0x40)
							NDS_makeARM7Int(6);
						MMU.timer[1][3] += MMU.timerReload[1][3];
					}
				}
				break;
			default :
				{
					nds.diff = (nds.cycles - nds.timerCycle[1][3])>>MMU.timerMODE[1][3];
					nds.old = MMU.timer[1][3];
					MMU.timer[1][3] += nds.diff;
					nds.timerCycle[1][3] += nds.diff << MMU.timerMODE[1][3];
					nds.timerOver[1][3] = nds.old>MMU.timer[1][3];
					if(nds.timerOver[1][3])
					{
						if(T1ReadWord(MMU.ARM7_REG, 0x10E) & 0x40)
							NDS_makeARM7Int(6);
						MMU.timer[1][3] += MMU.timerReload[1][3];
					}
				}
				break;
			}
		}
		else
		{
			MMU.timerRUN[1][3] = TRUE;
			nds.timerCycle[1][3] = nds.cycles;
		}
	}
}

void NDS_exec_hframe(int cpu_clockdown_level_arm9, int cpu_clockdown_level_arm7)
{
	int h;
	for (h = 0; h < 2; h++)
	{
		s32 nb = nds.cycles + (h ? (99 * 12) : (256 * 12));

		while (nb > nds.ARM9Cycle && !NDS_ARM9.waitIRQ)
			nds.ARM9Cycle += armcpu_exec(&NDS_ARM9) << (cpu_clockdown_level_arm9);
		if (NDS_ARM9.waitIRQ) nds.ARM9Cycle = nb;
		while (nb > nds.ARM7Cycle && !NDS_ARM7.waitIRQ)
			nds.ARM7Cycle += armcpu_exec(&NDS_ARM7) << (1 + (cpu_clockdown_level_arm7));
		if (NDS_ARM7.waitIRQ) nds.ARM7Cycle = nb;
		nds.cycles = (nds.ARM9Cycle<nds.ARM7Cycle)?nds.ARM9Cycle : nds.ARM7Cycle;

		/* HBLANK */
		if (h)
		{
			T1WriteWord(ARM9Mem.ARM9_REG, 4, T1ReadWord(ARM9Mem.ARM9_REG, 4) | 2);
			T1WriteWord(MMU.ARM7_REG, 4, T1ReadWord(MMU.ARM7_REG, 4) | 2);
			NDS_ARM9HBlankInt();
			NDS_ARM7HBlankInt();

			if(nds.VCount<192)
			{
				if(MMU.DMAStartTime[0][0] == 2)
					MMU_doDMA(0, 0);
				if(MMU.DMAStartTime[0][1] == 2)
					MMU_doDMA(0, 1);
				if(MMU.DMAStartTime[0][2] == 2)
					MMU_doDMA(0, 2);
				if(MMU.DMAStartTime[0][3] == 2)
					MMU_doDMA(0, 3);
			}
		}
		else
		{
			/* HDISP */
			u32 vmatch;

			nds.nextHBlank += 4260;
			++nds.VCount;
			T1WriteWord(ARM9Mem.ARM9_REG, 4, T1ReadWord(ARM9Mem.ARM9_REG, 4) & 0xFFFD);
			T1WriteWord(MMU.ARM7_REG, 4, T1ReadWord(MMU.ARM7_REG, 4) & 0xFFFD);

			if(MMU.DMAStartTime[0][0] == 3)
				MMU_doDMA(0, 0);
			if(MMU.DMAStartTime[0][1] == 3)
				MMU_doDMA(0, 1);
			if(MMU.DMAStartTime[0][2] == 3)
				MMU_doDMA(0, 2);
			if(MMU.DMAStartTime[0][3] == 3)
				MMU_doDMA(0, 3);

			// Main memory display
			if(MMU.DMAStartTime[0][0] == 4)
			{
				MMU_doDMA(0, 0);
				MMU.DMAStartTime[0][0] = 0;
			}
			if(MMU.DMAStartTime[0][1] == 4)
			{
				MMU_doDMA(0, 1);
				MMU.DMAStartTime[0][1] = 0;
			}
			if(MMU.DMAStartTime[0][2] == 4)
			{
				MMU_doDMA(0, 2);
				MMU.DMAStartTime[0][2] = 0;
			}
			if(MMU.DMAStartTime[0][3] == 4)
			{
				MMU_doDMA(0, 3);
				MMU.DMAStartTime[0][3] = 0;
			}

			if(MMU.DMAStartTime[1][0] == 4)
			{
				MMU_doDMA(1, 0);
				MMU.DMAStartTime[1][0] = 0;
			}
			if(MMU.DMAStartTime[1][1] == 4)
			{
				MMU_doDMA(1, 1);
				MMU.DMAStartTime[0][1] = 0;
			}
			if(MMU.DMAStartTime[1][2] == 4)
			{
				MMU_doDMA(1, 2);
				MMU.DMAStartTime[1][2] = 0;
			}
			if(MMU.DMAStartTime[1][3] == 4)
			{
				MMU_doDMA(1, 3);
				MMU.DMAStartTime[1][3] = 0;
			}
                            
			if(nds.VCount == 192)
			{
				/* VBLANK */
				T1WriteWord(ARM9Mem.ARM9_REG, 4, T1ReadWord(ARM9Mem.ARM9_REG, 4) | 1);
				T1WriteWord(MMU.ARM7_REG, 4, T1ReadWord(MMU.ARM7_REG, 4) | 1);
				NDS_ARM9VBlankInt();
				NDS_ARM7VBlankInt();

				if(MMU.DMAStartTime[0][0] == 1)
					MMU_doDMA(0, 0);
				if(MMU.DMAStartTime[0][1] == 1)
					MMU_doDMA(0, 1);
				if(MMU.DMAStartTime[0][2] == 1)
					MMU_doDMA(0, 2);
				if(MMU.DMAStartTime[0][3] == 1)
					MMU_doDMA(0, 3);

				if(MMU.DMAStartTime[1][0] == 1)
					MMU_doDMA(1, 0);
				if(MMU.DMAStartTime[1][1] == 1)
					MMU_doDMA(1, 1);
				if(MMU.DMAStartTime[1][2] == 1)
					MMU_doDMA(1, 2);
				if(MMU.DMAStartTime[1][3] == 1)
					MMU_doDMA(1, 3);
			}
			else if(nds.VCount == 263)
			{
				const int cycles_per_frame = (263 * (99 * 12 + 256 * 12));
				/* VDISP */
				nds.nextHBlank = 3168;
				nds.VCount = 0;
				T1WriteWord(ARM9Mem.ARM9_REG, 4, T1ReadWord(ARM9Mem.ARM9_REG, 4) & 0xFFFE);
				T1WriteWord(MMU.ARM7_REG, 4, T1ReadWord(MMU.ARM7_REG, 4) & 0xFFFE);

				nds.cycles -= cycles_per_frame;
				nds.ARM9Cycle -= cycles_per_frame;
				nds.ARM7Cycle -= cycles_per_frame;
				nb -= cycles_per_frame;
				if(MMU.timerON[0][0])
					nds.timerCycle[0][0] -= cycles_per_frame;
				if(MMU.timerON[0][1])
					nds.timerCycle[0][1] -= cycles_per_frame;
				if(MMU.timerON[0][2])
					nds.timerCycle[0][2] -= cycles_per_frame;
				if(MMU.timerON[0][3])
					nds.timerCycle[0][3] -= cycles_per_frame;

				if(MMU.timerON[1][0])
					nds.timerCycle[1][0] -= cycles_per_frame;
				if(MMU.timerON[1][1])
					nds.timerCycle[1][1] -= cycles_per_frame;
				if(MMU.timerON[1][2])
					nds.timerCycle[1][2] -= cycles_per_frame;
				if(MMU.timerON[1][3])
					nds.timerCycle[1][3] -= cycles_per_frame;
				if(MMU.DMAing[0][0])
					MMU.DMACycle[0][0] -= cycles_per_frame;
				if(MMU.DMAing[0][1])
					MMU.DMACycle[0][1] -= cycles_per_frame;
				if(MMU.DMAing[0][2])
					MMU.DMACycle[0][2] -= cycles_per_frame;
				if(MMU.DMAing[0][3])
					MMU.DMACycle[0][3] -= cycles_per_frame;
				if(MMU.DMAing[1][0])
					MMU.DMACycle[1][0] -= cycles_per_frame;
				if(MMU.DMAing[1][1])
					MMU.DMACycle[1][1] -= cycles_per_frame;
				if(MMU.DMAing[1][2])
					MMU.DMACycle[1][2] -= cycles_per_frame;
				if(MMU.DMAing[1][3])
					MMU.DMACycle[1][3] -= cycles_per_frame;

			}

			T1WriteWord(ARM9Mem.ARM9_REG, 6, nds.VCount);
			T1WriteWord(MMU.ARM7_REG, 6, nds.VCount);

			vmatch = T1ReadWord(ARM9Mem.ARM9_REG, 4);
			if((nds.VCount==(vmatch>>8)|((vmatch<<1)&(1<<8))))
			{
				T1WriteWord(ARM9Mem.ARM9_REG, 4, T1ReadWord(ARM9Mem.ARM9_REG, 4) | 4);
				if(T1ReadWord(ARM9Mem.ARM9_REG, 4) & 32)
					NDS_makeARM9Int(2);
			}
			else
				T1WriteWord(ARM9Mem.ARM9_REG, 4, T1ReadWord(ARM9Mem.ARM9_REG, 4) & 0xFFFB);

			vmatch = T1ReadWord(MMU.ARM7_REG, 4);
			if((nds.VCount==(vmatch>>8)|((vmatch<<1)&(1<<8))))
			{
				T1WriteWord(MMU.ARM7_REG, 4, T1ReadWord(MMU.ARM7_REG, 4) | 4);
				if(T1ReadWord(MMU.ARM7_REG, 4) & 32)
					NDS_makeARM7Int(2);
			}
			else
				T1WriteWord(MMU.ARM7_REG, 4, T1ReadWord(MMU.ARM7_REG, 4) & 0xFFFB);

			timer_check();
			dma_check();

		}
	}
}

void NDS_exec_frame(int cpu_clockdown_level_arm9, int cpu_clockdown_level_arm7)
{
	int v;
	for (v = 0; v < 263; v++)
	{
		NDS_exec_hframe(cpu_clockdown_level_arm9, cpu_clockdown_level_arm7);
	}
}
