OBJS += libretro-common/vfs/vfs_implementation.o
CFLAGS += -DUSE_LIBRETRO_VFS
endif
+ifeq "$(ENABLE_ICACHE_EMULATION)" "1"
+CFLAGS += -DICACHE_EMULATION
+endif
OBJS += frontend/libretro.o
CFLAGS += -Ilibretro-common/include
CFLAGS += -DFRONTEND_SUPPORTS_RGB565
WANT_ZLIB ?= 1
HAVE_CHD ?= 1
USE_LIBRETRO_VFS ?= 0
+ENABLE_ICACHE_EMULATION ?= 1
# Dynarec options: lightrec, ari64
DYNAREC ?= lightrec
else if (strcmp(var.value, "enabled") == 0)
Config.RCntFix = 1;
}
+
+#ifdef ICACHE_EMULATION
+ var.value = NULL;
+ var.key = "pcsx_rearmed_icache_emulation";
+
+ if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
+ {
+ if (strcmp(var.value, "disabled") == 0)
+ Config.icache_emulation = 0;
+ else if (strcmp(var.value, "enabled") == 0)
+ Config.icache_emulation = 1;
+ }
+#endif
var.value = NULL;
var.key = "pcsx_rearmed_inuyasha_fix";
},
"disabled",
},
+ {
+ "pcsx_rearmed_icache_emulation",
+ "Instruction Cache emulation",
+ "Enables or disables instruction cache emulation. Slower, but more accurate. Fails to run Spyro 2 PAL. This allows you to run F1 2001, Formula One Arcade, F1 99 and other games that may need instruction cache emulation. Interpreter/Lightrec only, does nothing on the ARMv7 backend.",
+ {
+ { "disabled", NULL },
+ { "enabled", NULL },
+ { NULL, NULL },
+ },
+ "disabled",
+ },
{
"pcsx_rearmed_inuyasha_fix",
"InuYasha Sengoku Battle Fix",
},
NULL
},
+ {
+ "pcsx_rearmed_icache_emulation",
+ "ICache Düzeltmleri",
+ NULL,
+ {
+ { NULL, NULL },
+ },
+ NULL
+ },
{
"pcsx_rearmed_inuyasha_fix",
"InuYasha Sengoku Battle Düzeltmesi",
{
// try to set sane config on which most games work
Config.Xa = Config.Cdda = Config.Sio =
- Config.SpuIrq = Config.RCntFix = Config.VSyncWA = 0;
+ Config.icache_emulation = Config.SpuIrq = Config.RCntFix = Config.VSyncWA = 0;
Config.PsxAuto = 1;
pl_rearmed_cbs.thread_rendering = 0;
CE_CONFIG_VAL(SpuIrq),
CE_CONFIG_VAL(RCntFix),
CE_CONFIG_VAL(VSyncWA),
+ CE_CONFIG_VAL(icache_emulation),
CE_CONFIG_VAL(Cpu),
CE_INTVAL(region),
CE_INTVAL_V(g_scaler, 3),
"Might be useful to overcome some dynarec bugs";
static const char h_cfg_shacks[] = "Breaks games but may give better performance\n"
"must reload game for any change to take effect";
+static const char h_cfg_icache[] = "Allows you to play the F1 games.\n"
+ "Note: This breaks the PAL version of Spyro 2.";
static menu_entry e_menu_adv_options[] =
{
mee_onoff_h ("Disable CD Audio", 0, Config.Cdda, 1, h_cfg_cdda),
//mee_onoff_h ("SIO IRQ Always Enabled", 0, Config.Sio, 1, h_cfg_sio),
mee_onoff_h ("SPU IRQ Always Enabled", 0, Config.SpuIrq, 1, h_cfg_spuirq),
+#ifdef ICACHE_EMULATION
+ mee_onoff_h ("ICache emulation", 0, Config.icache_emulation, 1, h_cfg_icache),
+#endif
//mee_onoff_h ("Rootcounter hack", 0, Config.RCntFix, 1, h_cfg_rcnt1),
mee_onoff_h ("Rootcounter hack 2", 0, Config.VSyncWA, 1, h_cfg_rcnt2),
mee_onoff_h ("Disable dynarec (slow!)",0, Config.Cpu, 1, h_cfg_nodrc),
lightrec_invalidate(lightrec_state, addr, size * 4);
}
+#ifdef ICACHE_EMULATION
+static void lightrec_plugin_notify(int note, void *data)
+{
+ /*
+ To change once proper icache emulation is emulated
+ switch (note)
+ {
+ case R3000ACPU_NOTIFY_CACHE_UNISOLATED:
+ lightrec_plugin_clear(0, 0x200000/4);
+ break;
+ case R3000ACPU_NOTIFY_CACHE_ISOLATED:
+ // Sent from psxDma3().
+ case R3000ACPU_NOTIFY_DMA3_EXE_LOAD:
+ default:
+ break;
+ }*/
+}
+#endif
+
static void lightrec_plugin_shutdown(void)
{
lightrec_destroy(lightrec_state);
lightrec_plugin_execute,
lightrec_plugin_execute_block,
lightrec_plugin_clear,
+#ifdef ICACHE_EMULATION
+ lightrec_plugin_notify,
+#endif
lightrec_plugin_shutdown,
};
invalidate_block(start);
}
+#ifdef ICACHE_EMULATION
+static void ari64_notify(int note, void *data) {
+ /*
+ To change once we have proper icache emulation
+ switch (note)
+ {
+ case R3000ACPU_NOTIFY_CACHE_UNISOLATED:
+ ari64_clear(0, 0x200000/4);
+ break;
+ case R3000ACPU_NOTIFY_CACHE_ISOLATED:
+ // Sent from psxDma3().
+ case R3000ACPU_NOTIFY_DMA3_EXE_LOAD:
+ default:
+ break;
+ }*/
+}
+#endif
+
static void ari64_shutdown()
{
new_dynarec_cleanup();
intExecuteBlockT,
#endif
ari64_clear,
+#ifdef ICACHE_EMULATION
+ ari64_notify,
+#endif
ari64_shutdown
};
}
//if no new request the pad return 0xff, for signaling connected
- if (reqPos >= respSize) return 0xff;
+ if (reqPos >= respSize
+#ifdef ICACHE_EMULATION
+ && writeok
+#endif
+ ) return 0xff;
switch(reqPos){
case 2:
#ifdef PSXBIOS_LOG
PSXBIOS_LOG("psxBios_%s\n", biosA0n[0x44]);
#endif
-
+#ifdef ICACHE_EMULATION
+ psxCpu->Notify(R3000ACPU_NOTIFY_CACHE_ISOLATED, NULL);
+ psxCpu->Notify(R3000ACPU_NOTIFY_CACHE_UNISOLATED, NULL);
+#endif
pc0 = ra;
}
boolean RCntFix;
boolean UseNet;
boolean VSyncWA;
+ boolean icache_emulation;
u8 Cpu; // CPU_DYNAREC or CPU_INTERPRETER
u8 PsxType; // PSX_TYPE_NTSC or PSX_TYPE_PAL
#ifdef _WIN32
void (*psxCP2[64])(struct psxCP2Regs *regs);
void (*psxCP2BSC[32])();
+#ifdef ICACHE_EMULATION
+/*
+Formula One 2001 :
+Use old CPU cache code when the RAM location is updated with new code (affects in-game racing)
+*/
+static u8* ICache_Addr;
+static u8* ICache_Code;
+uint32_t *Read_ICache(uint32_t pc)
+{
+ uint32_t pc_bank, pc_offset, pc_cache;
+ uint8_t *IAddr, *ICode;
+
+ pc_bank = pc >> 24;
+ pc_offset = pc & 0xffffff;
+ pc_cache = pc & 0xfff;
+
+ IAddr = ICache_Addr;
+ ICode = ICache_Code;
+
+ // cached - RAM
+ if (pc_bank == 0x80 || pc_bank == 0x00)
+ {
+ if (SWAP32(*(uint32_t *)(IAddr + pc_cache)) == pc_offset)
+ {
+ // Cache hit - return last opcode used
+ return (uint32_t *)(ICode + pc_cache);
+ }
+ else
+ {
+ // Cache miss - addresses don't match
+ // - default: 0xffffffff (not init)
+
+ // cache line is 4 bytes wide
+ pc_offset &= ~0xf;
+ pc_cache &= ~0xf;
+
+ // address line
+ *(uint32_t *)(IAddr + pc_cache + 0x0) = SWAP32(pc_offset + 0x0);
+ *(uint32_t *)(IAddr + pc_cache + 0x4) = SWAP32(pc_offset + 0x4);
+ *(uint32_t *)(IAddr + pc_cache + 0x8) = SWAP32(pc_offset + 0x8);
+ *(uint32_t *)(IAddr + pc_cache + 0xc) = SWAP32(pc_offset + 0xc);
+
+ // opcode line
+ pc_offset = pc & ~0xf;
+ *(uint32_t *)(ICode + pc_cache + 0x0) = psxMu32ref(pc_offset + 0x0);
+ *(uint32_t *)(ICode + pc_cache + 0x4) = psxMu32ref(pc_offset + 0x4);
+ *(uint32_t *)(ICode + pc_cache + 0x8) = psxMu32ref(pc_offset + 0x8);
+ *(uint32_t *)(ICode + pc_cache + 0xc) = psxMu32ref(pc_offset + 0xc);
+ }
+ }
+
+ /*
+ TODO: Probably should add cached BIOS
+ */
+ // default
+ return (uint32_t *)PSXM(pc);
+}
+#endif
+
static void delayRead(int reg, u32 bpc) {
u32 rold, rnew;
u32 *code;
u32 tmp;
- code = (u32 *)PSXM(bpc);
+ #ifdef ICACHE_EMULATION
+ if (Config.icache_emulation)
+ {
+ code = Read_ICache(psxRegs.pc);
+ }
+ else
+ #endif
+ {
+ code = (u32 *)PSXM(psxRegs.pc);
+ }
tmp = ((code == NULL) ? 0 : SWAP32(*code));
branch = 1;
u32 *code;
u32 temp;
- code = (u32 *)PSXM(psxRegs.pc);
+ #ifdef ICACHE_EMULATION
+ if (Config.icache_emulation)
+ {
+ code = Read_ICache(psxRegs.pc);
+ }
+ else
+ #endif
+ {
+ code = (u32 *)PSXM(psxRegs.pc);
+ }
psxRegs.code = ((code == NULL) ? 0 : SWAP32(*code));
switch (_Op_) {
case 0x00: // SPECIAL
if (psxDelayBranchTest(tar))
return;
- code = (u32 *)PSXM(psxRegs.pc);
+ #ifdef ICACHE_EMULATION
+ if (Config.icache_emulation)
+ {
+ code = Read_ICache(psxRegs.pc);
+ }
+ else
+ #endif
+ {
+ code = (u32 *)PSXM(psxRegs.pc);
+ }
psxRegs.code = ((code == NULL) ? 0 : SWAP32(*code));
debugI();
///////////////////////////////////////////
static int intInit() {
+#ifdef ICACHE_EMULATION
+ if (!ICache_Addr)
+ {
+ ICache_Addr = malloc(0x1000);
+ if (!ICache_Addr)
+ {
+ return -1;
+ }
+ }
+
+ if (!ICache_Code)
+ {
+ ICache_Code = malloc(0x1000);
+ if (!ICache_Code)
+ {
+ return -1;
+ }
+ }
+ memset(ICache_Addr, 0xff, 0x1000);
+ memset(ICache_Code, 0xff, 0x1000);
+#endif
return 0;
}
static void intReset() {
+#ifdef ICACHE_EMULATION
+ memset(ICache_Addr, 0xff, 0x1000);
+ memset(ICache_Code, 0xff, 0x1000);
+#endif
}
void intExecute() {
static void intClear(u32 Addr, u32 Size) {
}
+void intNotify (int note, void *data) {
+#ifdef ICACHE_EMULATION
+ /* Gameblabla - Only clear the icache if it's isolated */
+ if (note == R3000ACPU_NOTIFY_CACHE_ISOLATED)
+ {
+ memset(ICache_Addr, 0xff, 0x1000);
+ memset(ICache_Code, 0xff, 0x1000);
+ }
+#endif
+}
+
static void intShutdown() {
+#ifdef ICACHE_EMULATION
+ if (ICache_Addr)
+ {
+ free(ICache_Addr);
+ ICache_Addr = NULL;
+ }
+
+ if (ICache_Code)
+ {
+ free(ICache_Code);
+ ICache_Code = NULL;
+ }
+#endif
}
// interpreter execution
void execI() {
+#ifndef ICACHE_EMULATION
u32 *code = (u32 *)PSXM(psxRegs.pc);
+#else
+ u32 *code = Read_ICache(psxRegs.pc);
+#endif
+
psxRegs.code = ((code == NULL) ? 0 : SWAP32(*code));
debugI();
intExecute,
intExecuteBlock,
intClear,
+#ifdef ICACHE_EMULATION
+ intNotify,
+#endif
intShutdown
};
#define MAP_ANONYMOUS MAP_ANON
#endif
+boolean writeok = TRUE;
+
#ifndef NDEBUG
#include "debug.h"
#else
free(psxMemWLUT); psxMemWLUT = NULL;
}
-static int writeok = 1;
-
u8 psxMemRead8(u32 mem) {
char *p;
u32 t;
void psxMemWrite32(u32 mem, u32 value) {
char *p;
+#if defined(ICACHE_EMULATION)
+ /* Stores in PS1 code during cache isolation invalidate cachelines.
+ * It is assumed that cache-flush routines write to the lowest 4KB of
+ * address space for Icache, or 1KB for Dcache/scratchpad.
+ * Originally, stores had to check 'writeok' in psxRegs struct before
+ * writing to RAM. To eliminate this necessity, we could simply patch the
+ * BIOS 0x44 FlushCache() A0 jumptable entry. Unfortunately, this won't
+ * work for some games that use less-buggy non-BIOS cache-flush routines
+ * like '007 Tomorrow Never Dies', often provided by SN-systems, the PS1
+ * toolchain provider.
+ * Instead, we backup the lowest 64KB PS1 RAM when the cache is isolated.
+ * All stores write to RAM regardless of cache state. Thus, cache-flush
+ * routines temporarily trash the lowest 4KB of PS1 RAM. Fortunately, they
+ * ran in a 'critical section' with interrupts disabled, so there's little
+ * worry of PS1 code ever reading the trashed contents.
+ * We point the relevant portions of psxMemRLUT[] to the 64KB backup while
+ * cache is isolated. This is in case the dynarec needs to recompile some
+ * code during isolation. As long as it reads code using psxMemRLUT[] ptrs,
+ * it should never see trashed RAM contents.
+ *
+ * -senquack, mips dynarec team, 2017
+ */
+ static u32 mem_bak[0x10000/4];
+#endif
u32 t;
-
+ u32 m = mem & 0xffff;
// if ((mem&0x1fffff) == 0x71E18 || value == 0x48088800) SysPrintf("t2fix!!\n");
t = mem >> 16;
if (t == 0x1f80 || t == 0x9f80 || t == 0xbf80) {
- if ((mem & 0xffff) < 0x400)
+ if (m < 0x400)
psxHu32ref(mem) = SWAPu32(value);
else
psxHwWrite32(mem, value);
switch (value) {
case 0x800: case 0x804:
- if (writeok == 0) break;
- writeok = 0;
+ if (writeok == FALSE) break;
+ writeok = FALSE;
memset(psxMemWLUT + 0x0000, 0, 0x80 * sizeof(void *));
memset(psxMemWLUT + 0x8000, 0, 0x80 * sizeof(void *));
memset(psxMemWLUT + 0xa000, 0, 0x80 * sizeof(void *));
+#ifdef ICACHE_EMULATION
+ /* Cache is now isolated, pending cache-flush sequence:
+ * Backup lower 64KB of PS1 RAM, adjust psxMemRLUT[].
+ */
+ memcpy((void*)mem_bak, (void*)psxM, sizeof(mem_bak));
+ psxMemRLUT[0x0000] = psxMemRLUT[0x0020] = psxMemRLUT[0x0040] = psxMemRLUT[0x0060] = (u8 *)mem_bak;
+ psxMemRLUT[0x8000] = psxMemRLUT[0x8020] = psxMemRLUT[0x8040] = psxMemRLUT[0x8060] = (u8 *)mem_bak;
+ psxMemRLUT[0xa000] = psxMemRLUT[0xa020] = psxMemRLUT[0xa040] = psxMemRLUT[0xa060] = (u8 *)mem_bak;
+ psxCpu->Notify(R3000ACPU_NOTIFY_CACHE_ISOLATED, NULL);
+#endif
break;
case 0x00: case 0x1e988:
- if (writeok == 1) break;
- writeok = 1;
+ if (writeok == TRUE) break;
+ writeok = TRUE;
for (i = 0; i < 0x80; i++) psxMemWLUT[i + 0x0000] = (void *)&psxM[(i & 0x1f) << 16];
memcpy(psxMemWLUT + 0x8000, psxMemWLUT, 0x80 * sizeof(void *));
memcpy(psxMemWLUT + 0xa000, psxMemWLUT, 0x80 * sizeof(void *));
+#ifdef ICACHE_EMULATION
+ /* Cache is now unisolated:
+ * Restore lower 64KB RAM contents and psxMemRLUT[].
+ */
+ memcpy((void*)psxM, (void*)mem_bak, sizeof(mem_bak));
+ psxMemRLUT[0x0000] = psxMemRLUT[0x0020] = psxMemRLUT[0x0040] = psxMemRLUT[0x0060] = (u8 *)psxM;
+ psxMemRLUT[0x8000] = psxMemRLUT[0x8020] = psxMemRLUT[0x8040] = psxMemRLUT[0x8060] = (u8 *)psxM;
+ psxMemRLUT[0xa000] = psxMemRLUT[0xa020] = psxMemRLUT[0xa040] = psxMemRLUT[0xa060] = (u8 *)psxM;
+ /* Dynarecs might take this opportunity to flush their code cache */
+ psxCpu->Notify(R3000ACPU_NOTIFY_CACHE_UNISOLATED, NULL);
+#endif
break;
default:
#ifdef PSXMEM_LOG
psxMemReset();
memset(&psxRegs, 0x00, sizeof(psxRegs));
-
+ writeok = TRUE;
psxRegs.pc = 0xbfc00000; // Start in bootstrap
psxRegs.CP0.r[12] = 0x10900000; // COP0 enabled | BEV = 1 | TS = 1
}
void psxException(u32 code, u32 bd) {
- if (!Config.HLE && ((((psxRegs.code = PSXMu32(psxRegs.pc)) >> 24) & 0xfe) == 0x4a)) {
+ #ifdef ICACHE_EMULATION
+ /* Without the CPU_INTERPRETER condition, this will make Lightrec crash.
+ * Hopefully a better solution than this mess is found. - Gameblabla
+ */
+ if (Config.icache_emulation && Config.Cpu == CPU_INTERPRETER)
+ {
+ psxRegs.code = SWAPu32(*Read_ICache(psxRegs.pc));
+ }
+ else
+ #endif
+ {
+ psxRegs.code = PSXMu32(psxRegs.pc);
+ }
+
+ if (!Config.HLE && ((((psxRegs.code) >> 24) & 0xfe) == 0x4a)) {
// "hokuto no ken" / "Crash Bandicot 2" ...
// BIOS does not allow to return to GTE instructions
// (just skips it, supposedly because it's scheduled already)
#include "psxcounters.h"
#include "psxbios.h"
+#ifdef ICACHE_EMULATION
+enum {
+ R3000ACPU_NOTIFY_CACHE_ISOLATED = 0,
+ R3000ACPU_NOTIFY_CACHE_UNISOLATED = 1,
+ R3000ACPU_NOTIFY_DMA3_EXE_LOAD = 2
+};
+extern uint32_t *Read_ICache(uint32_t pc);
+#endif
+
typedef struct {
int (*Init)();
void (*Reset)();
void (*Execute)(); /* executes up to a break */
void (*ExecuteBlock)(); /* executes up to a jump */
void (*Clear)(u32 Addr, u32 Size);
+#ifdef ICACHE_EMULATION
+ void (*Notify)(int note, void *data);
+#endif
void (*Shutdown)();
} R3000Acpu;
struct { u32 sCycle, cycle; } intCycle[32];
} psxRegisters;
+extern boolean writeok;
+
extern psxRegisters psxRegs;
/* new_dynarec stuff */