diff --git a/Src/CPU/PowerPC/ppc.cpp b/Src/CPU/PowerPC/ppc.cpp index 99274b4..44d4128 100644 --- a/Src/CPU/PowerPC/ppc.cpp +++ b/Src/CPU/PowerPC/ppc.cpp @@ -239,7 +239,7 @@ typedef struct { int interrupt_pending; int external_int; - UINT64 tb; /* 56-bit timebase register */ + UINT64 tb; /* 56-bit timebase register */ int (*irq_callback)(int irqline); @@ -247,12 +247,26 @@ typedef struct { PPC_FETCH_REGION * fetch; // STUFF added for the 6xx series - UINT32 dec, dec_frac; + UINT32 dec; UINT32 fpscr; FPR fpr[32]; UINT32 sr[16]; + // Timing related + int timer_ratio; + UINT32 timer_frac; + int tb_base_icount; + int dec_base_icount; + int dec_trigger_cycle; + + // Cycle related + UINT64 total_cycles; + int icount; + int cur_cycles; + int bus_freq_multiplier; + int cycles_per_second; + #if HAS_PPC603 int is603; #endif @@ -271,11 +285,6 @@ typedef struct { -static int ppc_icount; -static int ppc_tb_base_icount; -static int ppc_dec_base_icount; -static int ppc_dec_trigger_cycle; -static int bus_freq_multiplier = 1; static PPC_REGS ppc; static UINT32 ppc_rotate_mask[32][32]; @@ -424,53 +433,57 @@ INLINE UINT32 check_condition_code(UINT32 bo, UINT32 bi) INLINE UINT64 ppc_read_timebase(void) { - int cycles = ppc_tb_base_icount - ppc_icount; + int cycles = ppc.tb_base_icount - ppc.icount; - // timebase is incremented once every four core clock cycles, so adjust the cycles accordingly - return ppc.tb + (cycles / 4); + // Timebase is incremented according to timer ratio, so adjust value accordingly + return ppc.tb + (cycles / ppc.timer_ratio); } INLINE void ppc_write_timebase_l(UINT32 tbl) { - ppc_tb_base_icount = ppc_icount; + UINT64 tb = ppc_read_timebase(); - ppc.tb &= ~0xffffffff; - ppc.tb |= tbl; + ppc.tb_base_icount = ppc.icount + ((ppc.tb_base_icount - ppc.icount) % ppc.timer_ratio); + + ppc.tb = (tb&~0xffffffff)|tbl; } INLINE void ppc_write_timebase_h(UINT32 tbh) { - ppc_tb_base_icount = ppc_icount; + UINT64 tb = ppc_read_timebase(); - ppc.tb &= 0xffffffff; - ppc.tb |= (UINT64)(tbh) << 32; + ppc.tb_base_icount = ppc.icount + ((ppc.tb_base_icount - ppc.icount) % ppc.timer_ratio); + + ppc.tb = (tb&0xffffffff)|((UINT64)(tbh) << 32); } INLINE UINT32 read_decrementer(void) { - int cycles = ppc_dec_base_icount - ppc_icount; + int cycles = ppc.dec_base_icount - ppc.icount; - // decrementer is decremented once every four bus clock cycles, so adjust the cycles accordingly - return DEC - (cycles / (bus_freq_multiplier * 2)); + // Decrementer is decremented at same rate as timebase, so adjust value accordingly + return DEC - (cycles / ppc.timer_ratio); } INLINE void write_decrementer(UINT32 value) { - ppc_dec_base_icount = ppc_icount + (ppc_dec_base_icount - ppc_icount) % (bus_freq_multiplier * 2); + if (((value&0x80000000) && !(read_decrementer()&0x80000000))) + { + /* trigger interrupt */ + ppc.interrupt_pending |= 0x2; + ppc603_check_interrupts(); + } + ppc.dec_base_icount = ppc.icount + ((ppc.dec_base_icount - ppc.icount) % ppc.timer_ratio); + DEC = value; - // check if decrementer exception occurs during execution - if ((UINT32)(DEC - (ppc_icount / (bus_freq_multiplier * 2))) > (UINT32)(DEC)) - { - ppc_dec_trigger_cycle = ((ppc_icount / (bus_freq_multiplier * 2)) - DEC) * 4; - } + // Check if decrementer exception occurs during execution (exception occurs after decrementer + // has passed through zero) + if ((UINT32)(ppc.dec_base_icount / ppc.timer_ratio) > DEC) + ppc.dec_trigger_cycle = ppc.dec_base_icount - ((1 + DEC) * ppc.timer_ratio); else - { - ppc_dec_trigger_cycle = 0x7fffffff; - } - -// printf("DEC = %08X at %08X\n", value, ppc.pc); + ppc.dec_trigger_cycle = 0x7fffffff; } /*********************************************************************/ @@ -491,12 +504,6 @@ INLINE void ppc_set_spr(int spr, UINT32 value) case SPR_PVR: return; case SPR603E_DEC: - if(((value & 0x80000000) && !(DEC & 0x80000000)) || value == 0) - { - /* trigger interrupt */ - ppc.interrupt_pending |= 0x2; - ppc603_check_interrupts(); - } write_decrementer(value); return; @@ -821,13 +828,29 @@ void ppc_init(const PPC_CONFIG *config) multiplier = (float)((config->bus_frequency_multiplier >> 4) & 0xf) + (float)(config->bus_frequency_multiplier & 0xf) / 10.0f; - bus_freq_multiplier = (int)(multiplier * 2); + ppc.bus_freq_multiplier = (int)(multiplier * 2); + // tb and dec are incremented every four bus cycles, so calculate default timer ratio + ppc.timer_ratio = 2 * ppc.bus_freq_multiplier; + + switch (config->bus_frequency) + { + case BUS_FREQUENCY_16MHZ: ppc.cycles_per_second = multiplier * 16000000; break; + case BUS_FREQUENCY_20MHZ: ppc.cycles_per_second = multiplier * 20000000; break; + case BUS_FREQUENCY_25MHZ: ppc.cycles_per_second = multiplier * 25000000; break; + case BUS_FREQUENCY_33MHZ: ppc.cycles_per_second = multiplier * 33000000; break; + case BUS_FREQUENCY_40MHZ: ppc.cycles_per_second = multiplier * 40000000; break; + case BUS_FREQUENCY_50MHZ: ppc.cycles_per_second = multiplier * 50000000; break; + case BUS_FREQUENCY_60MHZ: ppc.cycles_per_second = multiplier * 60000000; break; + case BUS_FREQUENCY_66MHZ: ppc.cycles_per_second = multiplier * 66000000; break; + case BUS_FREQUENCY_75MHZ: ppc.cycles_per_second = multiplier * 75000000; break; + } + switch(config->pvr) { - case PPC_MODEL_603E: pll_config = mpc603e_pll_config[bus_freq_multiplier-1][config->bus_frequency]; break; - case PPC_MODEL_603EV: pll_config = mpc603ev_pll_config[bus_freq_multiplier-1][config->bus_frequency]; break; - case PPC_MODEL_603R: pll_config = mpc603r_pll_config[bus_freq_multiplier-1][config->bus_frequency]; break; + case PPC_MODEL_603E: pll_config = mpc603e_pll_config[ppc.bus_freq_multiplier-1][config->bus_frequency]; break; + case PPC_MODEL_603EV: pll_config = mpc603ev_pll_config[ppc.bus_freq_multiplier-1][config->bus_frequency]; break; + case PPC_MODEL_603R: pll_config = mpc603r_pll_config[ppc.bus_freq_multiplier-1][config->bus_frequency]; break; default: break; } @@ -861,6 +884,31 @@ void ppc_set_fetch(PPC_FETCH_REGION * fetch) ppc.fetch = fetch; } +UINT64 ppc_total_cycles(void) +{ + return ppc.total_cycles + (UINT64)(ppc.cur_cycles - ppc.icount); +} + +int ppc_get_cycles_per_sec() +{ + return ppc.cycles_per_second; +} + +int ppc_get_bus_freq_multipler() +{ + return ppc.bus_freq_multiplier; +} + +void ppc_set_timer_ratio(int ratio) +{ + ppc.timer_ratio = ratio; +} + +int ppc_get_timer_ratio() +{ + return ppc.timer_ratio; +} + /****************************************************************************** Supermodel Interface ******************************************************************************/ @@ -874,11 +922,10 @@ void ppc_save_state(CBlockFile *SaveState) { SaveState->NewBlock("PowerPC", __FILE__); - // Timer and decrementer - SaveState->Write(&ppc_icount, sizeof(ppc_icount)); - SaveState->Write(&ppc_tb_base_icount, sizeof(ppc_tb_base_icount)); - SaveState->Write(&ppc_dec_base_icount, sizeof(ppc_dec_base_icount)); - SaveState->Write(&ppc_dec_trigger_cycle, sizeof(ppc_dec_trigger_cycle)); + // Cycle counting + SaveState->Write(&ppc.icount, sizeof(ppc.icount)); + SaveState->Write(&ppc.cur_cycles, sizeof(ppc.cur_cycles)); + SaveState->Write(&ppc.total_cycles, sizeof(ppc.total_cycles)); // Registers SaveState->Write(ppc.r, sizeof(ppc.r)); @@ -939,7 +986,7 @@ void ppc_save_state(CBlockFile *SaveState) SaveState->Write(&ppc.tb, sizeof(ppc.tb)); SaveState->Write(&ppc.dec, sizeof(ppc.dec)); - SaveState->Write(&ppc.dec_frac, sizeof(ppc.dec_frac)); + SaveState->Write(&ppc.timer_frac, sizeof(ppc.timer_frac)); SaveState->Write(&ppc.fpscr, sizeof(ppc.fpscr)); SaveState->Write(ppc.fpr, sizeof(ppc.fpr)); @@ -955,10 +1002,9 @@ void ppc_load_state(CBlockFile *SaveState) } // Timer and decrementer - SaveState->Read(&ppc_icount, sizeof(ppc_icount)); - SaveState->Read(&ppc_tb_base_icount, sizeof(ppc_tb_base_icount)); - SaveState->Read(&ppc_dec_base_icount, sizeof(ppc_dec_base_icount)); - SaveState->Read(&ppc_dec_trigger_cycle, sizeof(ppc_dec_trigger_cycle)); + SaveState->Read(&ppc.icount, sizeof(ppc.icount)); + SaveState->Read(&ppc.cur_cycles, sizeof(ppc.cur_cycles)); + SaveState->Read(&ppc.total_cycles, sizeof(ppc.total_cycles)); // Registers SaveState->Read(ppc.r, sizeof(ppc.r)); @@ -1019,7 +1065,7 @@ void ppc_load_state(CBlockFile *SaveState) SaveState->Read(&ppc.tb, sizeof(ppc.tb)); SaveState->Read(&ppc.dec, sizeof(ppc.dec)); - SaveState->Read(&ppc.dec_frac, sizeof(ppc.dec_frac)); + SaveState->Read(&ppc.timer_frac, sizeof(ppc.timer_frac)); SaveState->Read(&ppc.fpscr, sizeof(ppc.fpscr)); SaveState->Read(ppc.fpr, sizeof(ppc.fpr)); @@ -1071,6 +1117,7 @@ void ppc_detach_debugger() Bus = PPCDebug->DetachBus(); PPCDebug = NULL; } +#endif // SUPERMODEL_DEBUGGER void ppc_set_pc(UINT32 pc) { @@ -1113,4 +1160,3 @@ UINT32 ppc_read_msr() { return ppc_get_msr(); } -#endif // SUPERMODEL_DEBUGGER \ No newline at end of file diff --git a/Src/CPU/PowerPC/ppc.h b/Src/CPU/PowerPC/ppc.h index 1e9ad61..e098530 100644 --- a/Src/CPU/PowerPC/ppc.h +++ b/Src/CPU/PowerPC/ppc.h @@ -352,6 +352,11 @@ extern void ppc_reset(void); extern void ppc_shutdown(void); extern void ppc_init(const PPC_CONFIG *config); // must be called second! extern void ppc_set_fetch(PPC_FETCH_REGION * fetch); +extern UINT64 ppc_total_cycles(void); +extern int ppc_get_cycles_per_sec(void); +extern int ppc_get_bus_freq_multipler(void); +extern int ppc_get_timer_ratio(void); +extern void ppc_set_timer_ratio(int ratio); // These have been added to support the new Supermodel extern void ppc_attach_bus(class CBus *BusPtr); // must be called first! @@ -367,6 +372,7 @@ extern UINT32 ppc_read_sr(unsigned num); // These have been added to support the Supermodel debugger extern void ppc_attach_debugger(class Debugger::CPPCDebug *PPCDebugPtr); extern void ppc_detach_debugger(); +#endif // SUPERMODEL_DEBUGGER extern void ppc_set_pc(UINT32 pc); extern UINT8 ppc_get_cr(unsigned num); extern void ppc_set_cr(unsigned num, UINT8 val); @@ -375,6 +381,4 @@ extern void ppc_set_fpr(unsigned num, double val); extern void ppc_write_spr(unsigned spr, UINT32 val); extern void ppc_write_sr(unsigned num, UINT32 val); extern UINT32 ppc_read_msr(); -#endif // SUPERMODEL_DEBUGGER - #endif // INCLUDED_PPC_H diff --git a/Src/CPU/PowerPC/ppc603.c b/Src/CPU/PowerPC/ppc603.c index 39c01e5..51587e4 100644 --- a/Src/CPU/PowerPC/ppc603.c +++ b/Src/CPU/PowerPC/ppc603.c @@ -247,25 +247,30 @@ void ppc_reset(void) ppc.hid0 = 1; ppc.interrupt_pending = 0; + + ppc.tb = 0; + ppc.timer_frac = 0; + DEC = 0xffffffff; + ppc.total_cycles = 0; + ppc.cur_cycles = 0; + ppc.icount = 0; } int ppc_execute(int cycles) { UINT32 opcode; - - ppc_icount = cycles; - ppc_tb_base_icount = cycles; - ppc_dec_base_icount = cycles + ppc.dec_frac; - // check if decrementer exception occurs during execution - if ((UINT32)(DEC - (cycles / (bus_freq_multiplier * 2))) > (UINT32)(DEC)) - { - ppc_dec_trigger_cycle = ((cycles / (bus_freq_multiplier * 2)) - DEC) * 4; - } + ppc.cur_cycles = cycles; + ppc.icount = cycles; + ppc.tb_base_icount = cycles + ppc.timer_frac; + ppc.dec_base_icount = cycles + ppc.timer_frac; + + // Check if decrementer exception occurs during execution (exception occurs after decrementer + // has passed through zero) + if ((UINT32)(ppc.dec_base_icount / ppc.timer_ratio) > DEC) + ppc.dec_trigger_cycle = ppc.dec_base_icount - ((1 + DEC) * ppc.timer_ratio); else - { - ppc_dec_trigger_cycle = 0x7fffffff; - } + ppc.dec_trigger_cycle = 0x7fffffff; ppc_change_pc(ppc.npc); @@ -276,8 +281,6 @@ int ppc_execute(int cycles) DisassemblePowerPC(opcode, ppc.npc, string1, string2, true); printf("%08X: %s %s\n", ppc.npc, string1, string2); }*/ -// printf("trigger cycle %d (%08X)\n", ppc_dec_trigger_cycle, ppc_dec_trigger_cycle); -// printf("tb = %08X %08X\n", (UINT32)(ppc.tb >> 32), (UINT32)(ppc.tb)); ppc603_check_interrupts(); @@ -286,7 +289,7 @@ int ppc_execute(int cycles) PPCDebug->CPUActive(); #endif // SUPERMODEL_DEBUGGER - while( ppc_icount > 0 && !ppc.fatalError) + while( ppc.icount > 0 && !ppc.fatalError) { ppc.pc = ppc.npc; @@ -319,14 +322,10 @@ int ppc_execute(int cycles) default: optable[opcode >> 26](opcode); break; } - ppc_icount--; + ppc.icount--; - // Updating TB four times per core cycle fixes VF3 timing but breaks other games (Daytona 2 too fast, Spikeout has some geometry flickering) - //ppc.tb += 4; - - if(ppc_icount == ppc_dec_trigger_cycle) + if (ppc.icount == ppc.dec_trigger_cycle) { -// printf("dec int at %d\n", ppc_icount); ppc.interrupt_pending |= 0x2; ppc603_check_interrupts(); } @@ -339,17 +338,11 @@ int ppc_execute(int cycles) PPCDebug->CPUInactive(); #endif // SUPERMODEL_DEBUGGER - // update timebase - // timebase is incremented once every four core clock cycles, so adjust the cycles accordingly - // NOTE: updating at the end of the time slice breaks things that try to wait on TBL. Performing - // the update inside the execution loop fixes VF3 and allows many decrementer patches to be - // removed but it adversely affects Spikeout and other games. - ppc.tb += ((ppc_tb_base_icount - ppc_icount) / 4); - - // update decrementer - ppc.dec_frac = ((ppc_dec_base_icount - ppc_icount) % (bus_freq_multiplier * 2)); - DEC -= ((ppc_dec_base_icount - ppc_icount) / (bus_freq_multiplier * 2)); - + // Update timebase and decrementer. Both are updated at same rate as specified by timer_ratio. + ppc.timer_frac = ((ppc.tb_base_icount - ppc.icount) % ppc.timer_ratio); + ppc.tb += ((ppc.tb_base_icount - ppc.icount) / ppc.timer_ratio); + DEC -= ((ppc.dec_base_icount - ppc.icount) / ppc.timer_ratio); + /* { char string1[200]; @@ -360,5 +353,9 @@ int ppc_execute(int cycles) } */ - return cycles - ppc_icount; + int executed = cycles - ppc.icount; + ppc.total_cycles += executed; + ppc.cur_cycles = 0; + ppc.icount = 0; + return executed; } diff --git a/Src/CPU/PowerPC/ppc_ops.c b/Src/CPU/PowerPC/ppc_ops.c index 8bb758f..54c32ff 100644 --- a/Src/CPU/PowerPC/ppc_ops.c +++ b/Src/CPU/PowerPC/ppc_ops.c @@ -2221,7 +2221,7 @@ static void ppc_fctiwx(UINT32 op) } else if(FPR(b).fd < (INT64)((INT32)0x80000000)) { - FPR(t).id = 0x80000000; + FPR(t).id = 0x80000000; // FPSCR[FR] = 1 // FPSCR[FI] = 1 // FPSCR[XX] = 1 @@ -2255,7 +2255,7 @@ static void ppc_fctiwzx(UINT32 op) if(r > (INT64)((INT32)0x7fffffff)) { - FPR(t).id = 0x7fffffff; + FPR(t).id = 0x7fffffff; // FPSCR[FR] = 0 // FPSCR[FI] = 1 // FPSCR[XX] = 1 @@ -2263,7 +2263,7 @@ static void ppc_fctiwzx(UINT32 op) } else if(r < (INT64)((INT32)0x80000000)) { - FPR(t).id = 0x80000000; + FPR(t).id = 0x80000000; // FPSCR[FR] = 1 // FPSCR[FI] = 1 // FPSCR[XX] = 1 @@ -2368,8 +2368,8 @@ static void ppc_frsqrtex(UINT32 op) SET_VXSNAN_1(FPR(b)); - FPR(t).fd = 1.0 / sqrt(FPR(b).fd); /* verify this */ - + FPR(t).fd = 1.0 / sqrt(FPR(b).fd); /* verify this */ + set_fprf(FPR(t)); if( RCBIT ) { SET_CR1(); @@ -2745,7 +2745,7 @@ static void ppc_fmaddsx(UINT32 op) SET_VXSNAN(FPR(a), FPR(b)); SET_VXSNAN_1(FPR(c)); - FPR(t).fd = (float)((FPR(a).fd * FPR(c).fd) + FPR(b).fd); + FPR(t).fd = (float)((FPR(a).fd * FPR(c).fd) + FPR(b).fd); set_fprf(FPR(t)); if( RCBIT ) { diff --git a/Src/Games.cpp b/Src/Games.cpp index 790bad4..4187ed3 100644 --- a/Src/Games.cpp +++ b/Src/Games.cpp @@ -1470,6 +1470,88 @@ const struct GameInfo g_Model3GameList[] = { NULL, false, NULL, 0, 0, 0, 0, 0, false } } }, + + // Scud Race Plus (No Revision -- original? Requires MPC106...) + { + "scudp1", + "scud", + "Scud Race Plus (First Version?)", + "Sega", + 1997, + 0x15, + 0x200000, // 2 MB of fixed CROM + true, // 64 MB of banked CROM (Mirror) + 0x2000000, // 32 MB of VROM + 0x800000, // 8 MB of sample ROMs + GAME_INPUT_COMMON|GAME_INPUT_VEHICLE|GAME_INPUT_VR|GAME_INPUT_SHIFT4, + 1, // DSB1 MPEG board + true, // drive board + + { + // Fixed CROM (mirroring behavior here is special and handled manually by CModel3) + { "CROM", false, "epr-20095", 0x44467BC1, 0x80000, 2, 0x0600000, 8, true }, + { "CROM", false, "epr-20094", 0x299B6257, 0x80000, 2, 0x0600002, 8, true }, + { "CROM", false, "epr-20093", 0x9A85C611, 0x80000, 2, 0x0600004, 8, true }, + { "CROM", false, "epr-20092", 0x6F9161C1, 0x80000, 2, 0x0600006, 8, true }, + + // Banked CROM0 + { "CROMxx", false, "mpr-19661.04", 0x8E3FD241, 0x400000, 2, 0x0000000, 8, true }, + { "CROMxx", false, "mpr-19660.03", 0xD999C935, 0x400000, 2, 0x0000002, 8, true }, + { "CROMxx", false, "mpr-19659.02", 0xC47E7002, 0x400000, 2, 0x0000004, 8, true }, + { "CROMxx", false, "mpr-19658.01", 0xD523235C, 0x400000, 2, 0x0000006, 8, true }, + + // Banked CROM1 + { "CROMxx", false, "mpr-19665.08", 0xF97C78F9, 0x400000, 2, 0x1000000, 8, true }, + { "CROMxx", false, "mpr-19664.07", 0xB9D11294, 0x400000, 2, 0x1000002, 8, true }, + { "CROMxx", false, "mpr-19663.06", 0xF6AF1CA4, 0x400000, 2, 0x1000004, 8, true }, + { "CROMxx", false, "mpr-19662.05", 0x3C700EFF, 0x400000, 2, 0x1000006, 8, true }, + + // Banked CROM2 + { "CROMxx", false, "mpr-19669.12", 0xCDC43C61, 0x400000, 2, 0x2000000, 8, true }, + { "CROMxx", false, "mpr-19668.11", 0x0B4DD8D5, 0x400000, 2, 0x2000002, 8, true }, + { "CROMxx", false, "mpr-19667.10", 0xA8676799, 0x400000, 2, 0x2000004, 8, true }, + { "CROMxx", false, "mpr-19666.09", 0xB53DC97F, 0x400000, 2, 0x2000006, 8, true }, + + // Banked CROM3 + { "CROMxx", false, "mpr-20100.16", 0xC99E2C01, 0x400000, 2, 0x3000000, 8, true }, + { "CROMxx", false, "mpr-20099.15", 0xFC9BD7D9, 0x400000, 2, 0x3000002, 8, true }, + { "CROMxx", false, "mpr-20098.14", 0x8355FA41, 0x400000, 2, 0x3000004, 8, true }, + { "CROMxx", false, "mpr-20097.13", 0x269A9DBE, 0x400000, 2, 0x3000006, 8, true }, + + // Video ROM + { "VROM", false, "mpr-19672.26", 0x588C29FD, 0x200000, 2, 0, 32, false }, + { "VROM", false, "mpr-19673.27", 0x156ABAA9, 0x200000, 2, 2, 32, false }, + { "VROM", false, "mpr-19674.28", 0xC7B0F98C, 0x200000, 2, 4, 32, false }, + { "VROM", false, "mpr-19675.29", 0xFF113396, 0x200000, 2, 6, 32, false }, + { "VROM", false, "mpr-19676.30", 0xFD852EAD, 0x200000, 2, 8, 32, false }, + { "VROM", false, "mpr-19677.31", 0xC6AC0347, 0x200000, 2, 10, 32, false }, + { "VROM", false, "mpr-19678.32", 0xB8819CFE, 0x200000, 2, 12, 32, false }, + { "VROM", false, "mpr-19679.33", 0xE126C3E3, 0x200000, 2, 14, 32, false }, + { "VROM", false, "mpr-19680.34", 0x00EA5CEF, 0x200000, 2, 16, 32, false }, + { "VROM", false, "mpr-19681.35", 0xC949325F, 0x200000, 2, 18, 32, false }, + { "VROM", false, "mpr-19682.36", 0xCE5CA065, 0x200000, 2, 20, 32, false }, + { "VROM", false, "mpr-19683.37", 0xE5856419, 0x200000, 2, 22, 32, false }, + { "VROM", false, "mpr-19684.38", 0x56F6EC97, 0x200000, 2, 24, 32, false }, + { "VROM", false, "mpr-19685.39", 0x42B49304, 0x200000, 2, 26, 32, false }, + { "VROM", false, "mpr-19686.40", 0x84EED592, 0x200000, 2, 28, 32, false }, + { "VROM", false, "mpr-19687.41", 0x776CE694, 0x200000, 2, 30, 32, false }, + + // Sound ROMs + { "SndProg", false, "epr-20096a.21",0x0FEF288B, 0x80000, 2, 0, 2, true }, + { "Samples", false, "mpr-19670.22", 0xBD31CC06, 0x400000, 2, 0x000000, 2, true }, + { "Samples", false, "mpr-20101.24", 0x66D1E31F, 0x400000, 2, 0x400000, 2, true }, + { "DSBProg", false, "epr-19612.2", 0x13978FD4, 0x20000, 2, 0, 2, false }, + { "DSBMPEG", false, "mpr-19603.57", 0xB1B1765F, 0x200000, 2, 0x000000, 2, false }, + { "DSBMPEG", false, "mpr-19604.58", 0x6AC85B49, 0x200000, 2, 0x200000, 2, false }, + { "DSBMPEG", false, "mpr-19605.59", 0xBEC891EB, 0x200000, 2, 0x400000, 2, false }, + { "DSBMPEG", false, "mpr-19606.60", 0xADAD46B2, 0x200000, 2, 0x600000, 2, false }, + + // Drive Board ROM + { "DriveBd", true, "epr-19338a.bin", 0xC9FAC464, 0x10000, 2, 0, 2, false }, + + { NULL, false, NULL, 0, 0, 0, 0, 0, false } + } + }, // Ski Champ { diff --git a/Src/Model3/Model3.cpp b/Src/Model3/Model3.cpp index 282d462..7df6609 100644 --- a/Src/Model3/Model3.cpp +++ b/Src/Model3/Model3.cpp @@ -26,9 +26,12 @@ * * To-Do List * ---------- + * - Save state format has changed slightly. No longer need dmaUnknownRegister + * in Real3D.cpp. PowerPC timing variables have changed. Before 0.3a + * release, important to change format version #. * - ROM sets should probably be handled with a class that manages ROM * loading, the game list, as well as ROM patching - * - Wrap up CPU emulation inside a class (hah!) + * - Wrap up CPU emulation inside a class. * - Update the to-do list! I forgot lots of other stuff here :) * * PowerPC Address Map (may be slightly out of date/incomplete) @@ -1983,15 +1986,40 @@ void CModel3::RunMainBoardFrame(void) UINT32 start = CThread::GetTicks(); // Compute display and VBlank timings - unsigned frameCycles = g_Config.GetPowerPCFrequency()*1000000/60; - unsigned vblCycles = (unsigned) ((float) frameCycles * 2.5f/100.0f); // 2.5% vblank (ridiculously short and wrong but bigger values cause flicker in Daytona) + unsigned ppcCycles = g_Config.GetPowerPCFrequency() * 1000000; + unsigned frameCycles = ppcCycles / 60; + unsigned vblCycles = (unsigned)((float) frameCycles * 2.5f/100.0f); // 2.5% vblank (ridiculously short and wrong but bigger values cause flicker in Daytona) unsigned dispCycles = frameCycles - vblCycles; + // Scale PPC timer ratio according to speed at which the PowerPC is being emulated so that the observed running frequency of the PPC timer + // registers is more or less correct. This is needed to get the Virtua Striker 2 series of games running at the right speed (they are + // too slow otherwise). Other games appear to not be affected by this ratio so much as their running speed depends more on the timing of + // the Real3D status bit below. + ppc_set_timer_ratio(ppc_get_bus_freq_multipler() * 2 * ppcCycles / ppc_get_cycles_per_sec()); + + // Compute timing of the Real3D status bit. This value directly affects the speed at which all the games except Virtua Stiker 2 run. + // Currently it is not known exactly what this bit represents nor why such wildly varying values are needed for the different step models. + // The values below were arrived at by trial and error and clearly more investigation is required. If it turns out that the status bit is + // connected to the end of VBlank then the code below should be removed and the timing handled via GPU.VBlankEnd() instead. + unsigned statusCycles; + if (Game->step >= 0x20) + { + // For some reason, Fighting Vipers 2 and Daytona USA 2 require completely different timing to the rest of the step 2.x games + if (!strcmp(Game->id, "daytona2") || (Game->step == 0x20 && !strcmp(Game->id, "fvipers2"))) + statusCycles = (unsigned)((float)frameCycles * 24.0f/100.0f); + else + statusCycles = (unsigned)((float)frameCycles * 9.12f/100.0f); + } + else if (Game->step == 0x15) + statusCycles = (unsigned)((float)frameCycles * 4.8f/100.0f); + else + statusCycles = (unsigned)((float)frameCycles * 48.0f/100.0f); + // VBlank if (gpusReady) { TileGen.BeginVBlank(); - GPU.BeginVBlank(); + GPU.BeginVBlank(statusCycles); IRQ.Assert(0x02); ppc_execute(vblCycles); //printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr()); @@ -2020,7 +2048,8 @@ void CModel3::RunMainBoardFrame(void) ppc_execute(200); // give PowerPC time to acknowledge IRQ IRQ.Deassert(0x40); ppc_execute(200); // acknowledge that IRQ was deasserted (TODO: is this really needed?) - + dispCycles -= 400; + ++irqCount; if (irqCount > 128) { @@ -2640,15 +2669,7 @@ void CModel3::Reset(void) // Apply patches to games void CModel3::Patch(void) { - if (!strcmp(Game->id, "vf3") || !strcmp(Game->id, "vf3a")) - { - // Base offset of program in CROM: 0x710000 - //*(UINT32 *) &crom[0x713C7C] = 0x60000000; // this patch may not be needed anymore (find out why) - *(UINT32 *) &crom[0x713E54] = 0x60000000; // affects timing but prevents game from coining up -- investigate this carefully - //*(UINT32 *) &crom[0x7125B0] = 0x60000000; // this patch may not be needed anymore (find out why) - //*(UINT32 *) &crom[0x7125D0] = 0x60000000; // this patch may not be needed anymore (find out why) - } - else if (!strcmp(Game->id, "lemans24")) + if (!strcmp(Game->id, "lemans24")) { // Base offset of program in CROM: 6473C0 *(UINT32 *) &crom[0x6D8C4C] = 0x00000002; // comm. mode: 00=master, 01=slave, 02=satellite @@ -2656,54 +2677,8 @@ void CModel3::Patch(void) *(UINT32 *) &crom[0x73EB5C] = 0x60000000; *(UINT32 *) &crom[0x73EDD0] = 0x60000000; *(UINT32 *) &crom[0x73EDC4] = 0x60000000; - //*(UINT32 *) &crom[0x6473C0+0xF8BD0] = 0x60000000; // waiting for something from network card, called at F8CD8 - //*(UINT32 *) &crom[0x6473C0+0xF8B80] = 0x60000000; // "", called at 0xF8D90 - } - else if (!strcmp(Game->id, "scud")) - { - // Base offset of program in CROM: 0x710000 - *(UINT32 *) &crom[0x712734] = 0x60000000; // skips some ridiculously slow delay loop during boot-up - *(UINT32 *) &crom[0x71AEBC] = 0x60000000; // waiting for some flag in RAM that never gets modified (IRQ problem? try emulating VBL on Real3D) - *(UINT32 *) &crom[0x712268] = 0x60000000; // this corrects the boot-up menu (but why?) - crom[0x787B36^3] = 0x00; // Link ID: 00=single, 01=master, 02=slave (can bypass network board error) - *(UINT32 *) &crom[0x71277C] = 0x60000000; // seems to allow the game to start - *(UINT32 *) &crom[0x74072C] = 0x60000000; // ... ditto - //*(UINT32 *)&crom[0x799DE8] = 0x00050208; // debug menu - } - else if (!strcmp(Game->id, "scuda")) - { - *(UINT32 *) &crom[0x712734] = 0x60000000; // skips some ridiculously slow delay loop during boot-up - } - else if (!strcmp(Game->id, "scudj")) - { - *(UINT32 *) &crom[0x7126C8] = 0x60000000; // skips some ridiculously slow delay loop during boot-up - } - else if (!strcmp(Game->id, "scudp")) - { - /* - * RAM program structure: - * - * 1540: Reset vector transfers control here. Effective start of - * program. On error, game often resets here. - * 14844: Appears to be beginning of the actual boot-up process. - */ - - // Base offset of program in CROM: 710000 - // *(UINT32 *) &crom[0x713724] = 0x60000000; - // *(UINT32 *) &crom[0x713744] = 0x60000000; - // *(UINT32 *) &crom[0x741f48] = 0x60000000; - - *(UINT32 *) &crom[0x741f68] = 0x60000000; - *(UINT32 *) &crom[0x7126B8] = 0x60000000; // waits for something in RAM - - crom[0x7C62B2^3] = 0x00; // link ID is copied to 0x10011E, set it to single - } - else if (!strcmp(Game->id, "von2")) - { - *(UINT32 *) &crom[0x1B0] = 0x7C631A78; // eliminate annoyingly long delay loop - *(UINT32 *) &crom[0x1B4] = 0x60000000; // "" - } + } else if (!strcmp(Game->id, "lostwsga")) { *(UINT32 *) &crom[0x7374f4] = 0x38840004; // an actual bug in the game code @@ -2739,134 +2714,43 @@ void CModel3::Patch(void) *(UINT32 *) &crom[0x68468c] = 0x60000000; // protection device *(UINT32 *) &crom[0x6063c4] = 0x60000000; // needed to allow levels to load *(UINT32 *) &crom[0x616434] = 0x60000000; // prevents PPC from executing invalid code (MMU?) - *(UINT32 *) &crom[0x69f4e4] = 0x60000000; // "" - *(UINT32 *) &crom[0x600000+0x4C744] = 0x60000000; // decrementer loop? + *(UINT32 *) &crom[0x69f4e4] = 0x60000000; // "" } else if (!strcmp(Game->id, "dayto2pe")) { - *(UINT32 *) &crom[0x606784] = 0x60000000; + //*(UINT32 *) &crom[0x606784] = 0x60000000; *(UINT32 *) &crom[0x69A3FC] = 0x60000000; // MAME says: jump to encrypted code - *(UINT32 *) &crom[0x618B28] = 0x60000000; // MAME says: jump to encrypted code - *(UINT32 *) &crom[0x64CA34] = 0x60000000; // decrementer + *(UINT32 *) &crom[0x618B28] = 0x60000000; // MAME says: jump to encrypted code } - else if (!strcmp(Game->id, "fvipers2")) - { - /* - * Game code is copied to RAM in a non-trivial fashion (it may be - * compressed) just prior to the following sequence of code, which then - * transfers control to the RAM program: - * - * FFF0153C: 3C200070 li r1,0x00700000 - * FFF01540: 3C60FF80 li r3,0xFF800000 - * FFF01544: 38800000 li r4,0x00000000 - * FFF01548: 48000751 bl 0xFFF01C98 - * FFF0154C: 7C7F42A6 mfspr r3,pvr - * FFF01550: 90600080 stw r3,0x00000080 - * FFF01554: 92E00084 stw r23,0x00000084 - * FFF01558: 38600100 li r3,0x00000100 - * FFF0155C: 7C6803A6 mtspr lr,r3 - * FFF01560: 4E800020 bclr 0x14,0 - * - * In order to patch the necessary portions of the RAM program, we must - * insert a routine that executes after the program is loaded. There is - * ample room in the vector table between 0xFFF00004 and 0xFFF000FC. - * The patching routine must terminate with a "bclr 0x14,0" to jump to - * the RAM program. - */ - *(UINT32 *) &crom[0xFFF01560-0xFF800000] = 0x4BF00006; // ba 0xFFF00004 - *(UINT32 *) &crom[0xFFF00004-0xFF800000] = (31<<26)|(316<<1); // xor r0,r0,r0 ; R0 = 0 - *(UINT32 *) &crom[0xFFF00008-0xFF800000] = (15<<26)|(2<<21)|0x6000; // addis r2,0,0x6000 ; R2 = nop - *(UINT32 *) &crom[0xFFF0000C-0xFF800000] = (24<<26)|(1<<16)|0xA2E8; // ori r1,r0,0xA2E8 ; [A2E8] <- nop (decrementer loop) - *(UINT32 *) &crom[0xFFF00010-0xFF800000] = (36<<26)|(2<<21)|(1<<16); // stw r2,0(r1) - *(UINT32 *) &crom[0xFFF00014-0xFF800000] = 0x4E800020; // bclr 0x14,0 ; return to RAM code - - /* // Security board patch for Andy - *(UINT32 *) &crom[0xFFF01560-0xFF800000] = 0x4BF00006; // ba 0xFFF00004 - *(UINT32 *) &crom[0xFFF00004-0xFF800000] = (31<<26)|(316<<1); // xor r0,r0,r0 ; R0 = 0 - *(UINT32 *) &crom[0xFFF00008-0xFF800000] = (15<<26)|(2<<21)|0x3860; // addis r2,0,0x3860 ; R2 = "li r3,0xFFFFFFFF" - *(UINT32 *) &crom[0xFFF0000C-0xFF800000] = (24<<26)|(2<<21)|(2<<16)|0xFFFF; // ori r2,r2,0xFFFF - *(UINT32 *) &crom[0xFFF00010-0xFF800000] = (24<<26)|(1<<16)|0x9CF8; // ori r1,r0,0x9CF8 ; [9CF8] <- set return value to "success" - *(UINT32 *) &crom[0xFFF00014-0xFF800000] = (36<<26)|(2<<21)|(1<<16); // stw r2,0(r1) - *(UINT32 *) &crom[0xFFF00018-0xFF800000] = (15<<26)|(2<<21)|0x4800; // addis r2,0,0x4800 ; R2 = "ba 0x9D40" - *(UINT32 *) &crom[0xFFF0001C-0xFF800000] = (24<<26)|(2<<21)|(2<<16)|0x9D42; // ori r2,r2,0xFFFF - *(UINT32 *) &crom[0xFFF00020-0xFF800000] = (24<<26)|(1<<16)|0x9CFC; // ori r1,r0,0x9CFC ; [9CFC] <- jump to end of security board routine - *(UINT32 *) &crom[0xFFF00024-0xFF800000] = (36<<26)|(2<<21)|(1<<16); // stw r2,0(r1) - *(UINT32 *) &crom[0xFFF00028-0xFF800000] = 0x4E800020; // bclr 0x14,0 ; return to RAM code - */ - - // NOTE: At 32714, a test is made that determines the message: ONE PROCESSOR DETECTED, TWO "", etc. - } else if (!strcmp(Game->id, "harley")) { *(UINT32 *) &crom[0x50E8D4] = 0x60000000; *(UINT32 *) &crom[0x50E8F4] = 0x60000000; *(UINT32 *) &crom[0x50FB84] = 0x60000000; - - *(UINT32 *) &crom[0x4F736C] = 0x60000000; - *(UINT32 *) &crom[0x4F738C] = 0x60000000; } else if (!strcmp(Game->id, "harleyb")) { *(UINT32 *) &crom[0x50ECB4] = 0x60000000; *(UINT32 *) &crom[0x50ECD4] = 0x60000000; *(UINT32 *) &crom[0x50FF64] = 0x60000000; - - *(UINT32 *) &crom[0x4F774C] = 0x60000000; - *(UINT32 *) &crom[0x4F776C] = 0x60000000; - } - else if (!strcmp(Game->id, "oceanhun")) - { - // Base address of program in CROM: 588FD8-108FD8=480000 - //*(UINT32 *) &crom[0x480000+0x108FE0] = 0x60000000; // bad DMA copies from CROM - //*(UINT32 *) &crom[0x480000+0x112020] = 0x60000000; // reads from invalid addresses (due to CROM?) - *(UINT32 *) &crom[0x480000+0xF995C] = 0x60000000; // decrementer } else if (!strcmp(Game->id, "swtrilgy")) { *(UINT32 *) &crom[0xF0E48] = 0x60000000; - *(UINT32 *) &crom[0x043DC] = 0x48000090; + *(UINT32 *) &crom[0x043DC] = 0x48000090; // related to joystick feedback *(UINT32 *) &crom[0x029A0] = 0x60000000; *(UINT32 *) &crom[0x02A0C] = 0x60000000; } else if (!strcmp(Game->id, "swtrilgya")) { *(UINT32 *) &crom[0xF6DD0] = 0x60000000; // from MAME - - //*(UINT32 *) &crom[0xF1128] = 0x60000000; // these bypass required delay loops and break game timing - //*(UINT32 *) &crom[0xF10E0] = 0x60000000; } else if (!strcmp(Game->id, "eca") || !strcmp(Game->id, "ecax")) { + *(UINT32 *) &crom[0x535580] = 0x60000000; *(UINT32 *) &crom[0x5023B4] = 0x60000000; *(UINT32 *) &crom[0x5023D4] = 0x60000000; - - *(UINT32 *) &crom[0x535560] = 0x60000000; // decrementer loop - } - else if (!strcmp(Game->id, "spikeout")) - { - /* - * Decrementer loop at 0x31994 seems to work until a few frames into - * the attract mode and game, at which point a very large value is - * loaded into the decrementer and locks up the CPU (the usual - * decrementer problem). "Insert Coin" keeps flashing because it is - * managed via an IRQ, evidently. - * - * 0x00031994: 0x7C9602A6 mfspr r4,dec - * 0x00031998: 0x2C040000 cmpi cr0,0,r4,0x0000 - * 0x0003199C: 0x41A0FFF8 bt cr0[lt],0x00031994 - */ - - *(UINT32 *) &crom[0x600000+0x3199C] = 0x60000000; - } - else if (!strcmp(Game->id, "spikeofe")) - { - *(UINT32 *) &crom[0x600000+0x36F2C] = 0x60000000; // decrementer loop (see Spikeout) - } - else if (!strcmp(Game->id, "skichamp")) - { - // Base address of program in CROM: 0x480000 - *(UINT32 *) &crom[0x480000+0x96B9C] = 0x60000000; // decrementer loop } } @@ -3036,14 +2920,17 @@ bool CModel3::LoadROMSet(const struct GameInfo *GameList, const char *zipFile) PPCConfig.pvr = PPC_MODEL_603E; // 100 MHz PPCConfig.bus_frequency = BUS_FREQUENCY_66MHZ; PPCConfig.bus_frequency_multiplier = 0x15; // 1.5X multiplier - PCIBridge.SetModel(0x105); // MPC105 + if (!strcmp(Game->id, "scudp1")) // some Step 1.x games use MPC106 + PCIBridge.SetModel(0x106); + else + PCIBridge.SetModel(0x105); // MPC105 } else if (Game->step == 0x10) // Step 1.0 { PPCConfig.pvr = PPC_MODEL_603R; // 66 MHz PPCConfig.bus_frequency = BUS_FREQUENCY_66MHZ; PPCConfig.bus_frequency_multiplier = 0x10; // 1X multiplier - if (!strcmp(Game->id, "bass") || !strcmp(Game->id, "getbass")) // some Step 1.0 games use MPC106 + if (!strcmp(Game->id, "bass") || !strcmp(Game->id, "getbass")) // some Step 1.x games use MPC106 PCIBridge.SetModel(0x106); else PCIBridge.SetModel(0x105); // MPC105 @@ -3055,7 +2942,7 @@ bool CModel3::LoadROMSet(const struct GameInfo *GameList, const char *zipFile) ppc_init(&PPCConfig); ppc_attach_bus(this); - + PPCFetchRegions[0].start = 0; PPCFetchRegions[0].end = 0x007FFFFF; PPCFetchRegions[0].ptr = (UINT32 *) ram; diff --git a/Src/Model3/Real3D.cpp b/Src/Model3/Real3D.cpp index ea29d42..e746927 100644 --- a/Src/Model3/Real3D.cpp +++ b/Src/Model3/Real3D.cpp @@ -136,15 +136,17 @@ void CReal3D::LoadState(CBlockFile *SaveState) Rendering ******************************************************************************/ -void CReal3D::BeginVBlank(void) +void CReal3D::BeginVBlank(int statusCycles) { - status |= 2; // VBlank bit + // Calculate point at which status bit should change value. Currently the same timing is used for both the status bit in ReadRegister + // and in WriteDMARegister32/ReadDMARegister32, however it may be that they are completely unrelated. It appears that step 1.x games + // access just the former while step 2.x access the latter. It is not known yet what this bit/these bits actually represent. + statusChange = ppc_total_cycles() + statusCycles; } void CReal3D::EndVBlank(void) { error = false; // clear error (just needs to be done once per frame) - status &= ~2; } UINT32 CReal3D::SyncSnapshots(void) @@ -358,8 +360,9 @@ void CReal3D::WriteDMARegister32(unsigned reg, UINT32 data) } else if ((data&0x80000000)) { - dmaUnknownReg ^= 0xFFFFFFFF; - dmaData = dmaUnknownReg; + //dmaUnknownReg ^= 0xFFFFFFFF; + //dmaData = dmaUnknownReg; + dmaData = (ppc_total_cycles() >= statusChange ? 0x0 : 0xFFFFFFFF); // Not sure yet if it is just bit 2 as per ReadRegister above } break; case 0x14: // ? @@ -886,7 +889,10 @@ UINT32 CReal3D::ReadRegister(unsigned reg) { DebugLog("Real3D: Read reg %X\n", reg); if (reg == 0) + { + UINT32 status = (ppc_total_cycles() >= statusChange ? 0x0 : 0x2); return 0xFFFFFFFD|status; + } else return 0xFFFFFFFF; } @@ -945,7 +951,6 @@ void CReal3D::Reset(void) queuedUploadTexturesRO.clear(); fifoIdx = 0; - status = 0; vromTextureAddr = 0; vromTextureHeader = 0; tapState = 0; diff --git a/Src/Model3/Real3D.h b/Src/Model3/Real3D.h index d9e9ab7..f9ffc35 100644 --- a/Src/Model3/Real3D.h +++ b/Src/Model3/Real3D.h @@ -80,7 +80,7 @@ public: * * Must be called before the VBlank starts. */ - void BeginVBlank(void); + void BeginVBlank(int statusCycles); /* * EndVBlank(void) @@ -446,7 +446,7 @@ private: bool commandPortWrittenRO; // Read-only copy of flag // Status and command registers - UINT32 status; + UINT64 statusChange; // JTAG Test Access Port UINT64 tapCurrentInstruction; // latched IR (not always equal to IR) diff --git a/Src/OSD/SDL/Main.cpp b/Src/OSD/SDL/Main.cpp index ce271f4..401d3e4 100644 --- a/Src/OSD/SDL/Main.cpp +++ b/Src/OSD/SDL/Main.cpp @@ -683,6 +683,22 @@ static void DrawCrosshair(float x, float y, float r, float g, float b) glVertex2f(x+dist+height, y+(base/2.0f)*a); } +static void PrintGLError(GLenum error) +{ + switch (error) + { + case GL_INVALID_ENUM: printf("invalid enum\n"); break; + case GL_INVALID_VALUE: printf("invalid value\n"); break; + case GL_INVALID_OPERATION: printf("invalid operation\n"); break; + case GL_STACK_OVERFLOW: printf("stack overflow\n"); break; + case GL_STACK_UNDERFLOW: printf("stack underflow\n"); break; + case GL_OUT_OF_MEMORY: printf("out of memory\n"); break; + case GL_TABLE_TOO_LARGE: printf("table too large\n"); break; + case GL_NO_ERROR: break; + default: printf("unknown error\n"); break; + } +} + static void UpdateCrosshairs(CInputs *Inputs, unsigned showCrosshairs) { float x[2], y[2]; @@ -692,6 +708,7 @@ static void UpdateCrosshairs(CInputs *Inputs, unsigned showCrosshairs) return; // Set up the viewport and orthogonal projection + glUseProgram(0); // no shaders glViewport(xOffset, yOffset, xRes, yRes); glMatrixMode(GL_PROJECTION); glLoadIdentity(); @@ -700,8 +717,8 @@ static void UpdateCrosshairs(CInputs *Inputs, unsigned showCrosshairs) glLoadIdentity(); glDisable(GL_TEXTURE_2D); // no texture mapping glDisable(GL_BLEND); // no blending - glDisable(GL_DEPTH_TEST); // no Z-buffering needed - glUseProgram(NULL); // no shaders + glDisable(GL_DEPTH_TEST); // no Z-buffering needed + glDisable(GL_LIGHTING); // Convert gun coordinates to viewspace coordinates x[0] = (float) Inputs->gunX[0]->value; @@ -711,13 +728,15 @@ static void UpdateCrosshairs(CInputs *Inputs, unsigned showCrosshairs) GunToViewCoords(&x[0], &y[0]); GunToViewCoords(&x[1], &y[1]); - // Draw visible crosshairs + // Draw visible crosshairs glBegin(GL_TRIANGLES); if ((showCrosshairs & 1) && !Inputs->trigger[0]->offscreenValue) // Player 1 DrawCrosshair(x[0], y[0], 1.0f, 0.0f, 0.0f); - if ((showCrosshairs & 2) && !Inputs->trigger[1]->offscreenValue) // Player 2 + if ((showCrosshairs & 2) && !Inputs->trigger[1]->offscreenValue) // Player 2 DrawCrosshair(x[1], y[1], 0.0f, 1.0f, 0.0f); - glEnd(); + glEnd(); + + //PrintGLError(glGetError()); }