This rewrites the way that CPU alerts work, making them a bitmap (since multiple alerts can happen simultaneously, like SMC and IRQ). This doesn't really fix many games but improves accuracy overall and improves performance on some I/O writes (the ones without side effects). The IRQ raising is now decoupled and explicitely called via a new function (check_and_raise_interrupts) to avoid issues such as invalid CPSR values (doesn't seem to bother most games!). There's more side effects missing, so this just lays the ground for more fixes.
352 lines
9.1 KiB
C
352 lines
9.1 KiB
C
/* gameplaySP
|
|
*
|
|
* Copyright (C) 2006 Exophase <exophase@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include <ctype.h>
|
|
|
|
timer_type timer[4];
|
|
|
|
const u32 global_cycles_per_instruction = 1;
|
|
|
|
u32 cpu_ticks = 0;
|
|
|
|
u32 execute_cycles = 960;
|
|
s32 video_count = 960;
|
|
|
|
u32 last_frame = 0;
|
|
u32 flush_ram_count = 0;
|
|
u32 gbc_update_count = 0;
|
|
u32 oam_update_count = 0;
|
|
|
|
char main_path[512];
|
|
char save_path[512];
|
|
|
|
void trigger_ext_event(void);
|
|
|
|
static unsigned update_timers(irq_type *irq_raised, unsigned completed_cycles)
|
|
{
|
|
unsigned i, ret = 0;
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
if(timer[i].status == TIMER_INACTIVE)
|
|
continue;
|
|
|
|
if(timer[i].status != TIMER_CASCADE)
|
|
{
|
|
timer[i].count -= completed_cycles;
|
|
/* io_registers accessors range: REG_TM0D, REG_TM1D, REG_TM2D, REG_TM3D */
|
|
write_ioreg(REG_TM0D + (i * 2), -(timer[i].count > timer[i].prescale));
|
|
}
|
|
|
|
if(timer[i].count > 0)
|
|
continue;
|
|
|
|
/* irq_raised value range: IRQ_TIMER0, IRQ_TIMER1, IRQ_TIMER2, IRQ_TIMER3 */
|
|
if(timer[i].irq)
|
|
*irq_raised |= (8 << i);
|
|
|
|
if((i != 3) && (timer[i + 1].status == TIMER_CASCADE))
|
|
{
|
|
timer[i + 1].count--;
|
|
write_ioreg(REG_TM0D + (i + 1) * 2, -timer[i+1].count);
|
|
}
|
|
|
|
if(i < 2)
|
|
{
|
|
if(timer[i].direct_sound_channels & 0x01)
|
|
ret += sound_timer(timer[i].frequency_step, 0);
|
|
|
|
if(timer[i].direct_sound_channels & 0x02)
|
|
ret += sound_timer(timer[i].frequency_step, 1);
|
|
}
|
|
|
|
timer[i].count += (timer[i].reload << timer[i].prescale);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void init_main(void)
|
|
{
|
|
u32 i;
|
|
|
|
for(i = 0; i < 4; i++)
|
|
{
|
|
dma[i].start_type = DMA_INACTIVE;
|
|
dma[i].direct_sound_channel = DMA_NO_DIRECT_SOUND;
|
|
timer[i].status = TIMER_INACTIVE;
|
|
timer[i].reload = 0x10000;
|
|
}
|
|
|
|
timer[0].direct_sound_channels = TIMER_DS_CHANNEL_BOTH;
|
|
timer[1].direct_sound_channels = TIMER_DS_CHANNEL_NONE;
|
|
|
|
cpu_ticks = 0;
|
|
|
|
execute_cycles = 960;
|
|
video_count = 960;
|
|
|
|
#ifdef HAVE_DYNAREC
|
|
init_emitter(gamepak_must_swap());
|
|
init_caches();
|
|
#endif
|
|
}
|
|
|
|
u32 function_cc update_gba(int remaining_cycles)
|
|
{
|
|
irq_type irq_raised = IRQ_NONE;
|
|
int dma_cycles;
|
|
remaining_cycles = MAX(remaining_cycles, -64);
|
|
|
|
do
|
|
{
|
|
unsigned i;
|
|
// Number of cycles we ask to run - cycles that we did not execute
|
|
// (remaining_cycles can be negative and should be close to zero)
|
|
unsigned completed_cycles = execute_cycles - remaining_cycles;
|
|
cpu_ticks += completed_cycles;
|
|
|
|
remaining_cycles = 0;
|
|
reg[CHANGED_PC_STATUS] = 0;
|
|
reg[COMPLETED_FRAME] = 0;
|
|
|
|
if(gbc_sound_update)
|
|
{
|
|
gbc_update_count++;
|
|
update_gbc_sound(cpu_ticks);
|
|
gbc_sound_update = 0;
|
|
}
|
|
|
|
// Timers can trigger DMA (usually sound) and consume cycles
|
|
dma_cycles = update_timers(&irq_raised, completed_cycles);
|
|
|
|
video_count -= completed_cycles;
|
|
|
|
if(video_count <= 0)
|
|
{
|
|
u32 vcount = read_ioreg(REG_VCOUNT);
|
|
u32 dispstat = read_ioreg(REG_DISPSTAT);
|
|
|
|
if((dispstat & 0x02) == 0)
|
|
{
|
|
// Transition from hrefresh to hblank
|
|
video_count += (272);
|
|
dispstat |= 0x02;
|
|
|
|
if((dispstat & 0x01) == 0)
|
|
{
|
|
u32 i;
|
|
if(reg[OAM_UPDATED])
|
|
oam_update_count++;
|
|
|
|
update_scanline();
|
|
|
|
// If in visible area also fire HDMA
|
|
for(i = 0; i < 4; i++)
|
|
{
|
|
if(dma[i].start_type == DMA_START_HBLANK)
|
|
dma_transfer(i, &dma_cycles);
|
|
}
|
|
}
|
|
|
|
if(dispstat & 0x10)
|
|
irq_raised |= IRQ_HBLANK;
|
|
}
|
|
else
|
|
{
|
|
// Transition from hblank to next line
|
|
video_count += 960;
|
|
dispstat &= ~0x02;
|
|
|
|
vcount++;
|
|
|
|
if(vcount == 160)
|
|
{
|
|
// Transition from vrefresh to vblank
|
|
u32 i;
|
|
|
|
dispstat |= 0x01;
|
|
if(dispstat & 0x8)
|
|
{
|
|
irq_raised |= IRQ_VBLANK;
|
|
}
|
|
|
|
video_reload_counters();
|
|
|
|
for(i = 0; i < 4; i++)
|
|
{
|
|
if(dma[i].start_type == DMA_START_VBLANK)
|
|
dma_transfer(i, &dma_cycles);
|
|
}
|
|
}
|
|
else
|
|
|
|
if(vcount == 228)
|
|
{
|
|
// Transition from vblank to next screen
|
|
dispstat &= ~0x01;
|
|
|
|
/* printf("frame update (%x), %d instructions total, %d RAM flushes\n",
|
|
reg[REG_PC], instruction_count - last_frame, flush_ram_count);
|
|
last_frame = instruction_count;
|
|
*/
|
|
/* printf("%d gbc audio updates\n", gbc_update_count);
|
|
printf("%d oam updates\n", oam_update_count); */
|
|
gbc_update_count = 0;
|
|
oam_update_count = 0;
|
|
flush_ram_count = 0;
|
|
|
|
update_gbc_sound(cpu_ticks);
|
|
gbc_sound_update = 0;
|
|
|
|
/* If there's no cheat hook, run on vblank! */
|
|
if (cheat_master_hook == ~0U)
|
|
process_cheats();
|
|
|
|
vcount = 0;
|
|
// We completed a frame, tell the dynarec to exit to the main thread
|
|
reg[COMPLETED_FRAME] = 1;
|
|
}
|
|
|
|
if(vcount == (dispstat >> 8))
|
|
{
|
|
// vcount trigger
|
|
dispstat |= 0x04;
|
|
if(dispstat & 0x20)
|
|
{
|
|
irq_raised |= IRQ_VCOUNT;
|
|
}
|
|
}
|
|
else
|
|
dispstat &= ~0x04;
|
|
|
|
write_ioreg(REG_VCOUNT, vcount);
|
|
}
|
|
write_ioreg(REG_DISPSTAT, dispstat);
|
|
}
|
|
|
|
// Flag any V/H blank interrupts.
|
|
if (irq_raised)
|
|
flag_interrupt(irq_raised);
|
|
|
|
// Raise any pending interrupts. This changes the CPU mode.
|
|
check_and_raise_interrupts();
|
|
|
|
execute_cycles = MAX(video_count, 0);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
if(timer[i].status != TIMER_PRESCALE)
|
|
continue;
|
|
|
|
if(timer[i].count < execute_cycles)
|
|
execute_cycles = timer[i].count;
|
|
}
|
|
} while(reg[CPU_HALT_STATE] != CPU_ACTIVE && !reg[COMPLETED_FRAME]);
|
|
|
|
// We voluntarily limit this. It is not accurate but it would be much harder.
|
|
dma_cycles = MIN(64, dma_cycles);
|
|
dma_cycles = MIN(execute_cycles, dma_cycles);
|
|
|
|
return execute_cycles - dma_cycles;
|
|
}
|
|
|
|
void reset_gba(void)
|
|
{
|
|
init_memory();
|
|
init_main();
|
|
init_cpu();
|
|
reset_sound();
|
|
}
|
|
|
|
#ifdef TRACE_REGISTERS
|
|
void print_regs(void)
|
|
{
|
|
printf("R0=%08x R1=%08x R2=%08x R3=%08x "
|
|
"R4=%08x R5=%08x R6=%08x R7=%08x "
|
|
"R8=%08x R9=%08x R10=%08x R11=%08x "
|
|
"R12=%08x R13=%08x R14=%08x\n",
|
|
reg[0], reg[1], reg[2], reg[3],
|
|
reg[4], reg[5], reg[6], reg[7],
|
|
reg[8], reg[9], reg[10], reg[11],
|
|
reg[12], reg[13], reg[14]);
|
|
}
|
|
#endif
|
|
|
|
bool main_read_savestate(const u8 *src)
|
|
{
|
|
int i;
|
|
const u8 *p1 = bson_find_key(src, "emu");
|
|
const u8 *p2 = bson_find_key(src, "timers");
|
|
if (!p1 || !p2)
|
|
return false;
|
|
|
|
if (!(bson_read_int32(p1, "cpu-ticks", &cpu_ticks) &&
|
|
bson_read_int32(p1, "exec-cycles", &execute_cycles) &&
|
|
bson_read_int32(p1, "video-count", (u32*)&video_count)))
|
|
return false;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
char tname[2] = {'0' + i, 0};
|
|
const u8 *p = bson_find_key(p2, tname);
|
|
|
|
if (!(
|
|
bson_read_int32(p, "count", (u32*)&timer[i].count) &&
|
|
bson_read_int32(p, "reload", &timer[i].reload) &&
|
|
bson_read_int32(p, "prescale", &timer[i].prescale) &&
|
|
bson_read_int32(p, "freq-step", &timer[i].frequency_step) &&
|
|
bson_read_int32(p, "dsc", &timer[i].direct_sound_channels) &&
|
|
bson_read_int32(p, "irq", &timer[i].irq) &&
|
|
bson_read_int32(p, "status", &timer[i].status)))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
unsigned main_write_savestate(u8* dst)
|
|
{
|
|
int i;
|
|
u8 *wbptr, *wbptr2, *startp = dst;
|
|
bson_start_document(dst, "emu", wbptr);
|
|
bson_write_int32(dst, "cpu-ticks", cpu_ticks);
|
|
bson_write_int32(dst, "exec-cycles", execute_cycles);
|
|
bson_write_int32(dst, "video-count", video_count);
|
|
bson_finish_document(dst, wbptr);
|
|
|
|
bson_start_document(dst, "timers", wbptr);
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
char tname[2] = {'0' + i, 0};
|
|
bson_start_document(dst, tname, wbptr2);
|
|
bson_write_int32(dst, "count", timer[i].count);
|
|
bson_write_int32(dst, "reload", timer[i].reload);
|
|
bson_write_int32(dst, "prescale", timer[i].prescale);
|
|
bson_write_int32(dst, "freq-step", timer[i].frequency_step);
|
|
bson_write_int32(dst, "dsc", timer[i].direct_sound_channels);
|
|
bson_write_int32(dst, "irq", timer[i].irq);
|
|
bson_write_int32(dst, "status", timer[i].status);
|
|
bson_finish_document(dst, wbptr2);
|
|
}
|
|
bson_finish_document(dst, wbptr);
|
|
|
|
return (unsigned int)(dst - startp);
|
|
}
|
|
|
|
|