#include #include "mednafen/include/trio/trio.h" #include "mednafen/mednafen.h" #include "mednafen/git.h" #include "mednafen/general.h" #include "libretro.h" #include "thread.h" #include "mednafen/FileWrapper.h" #include "mednafen/pce_fast/pce.h" #include "mednafen/pce_fast/vdc.h" #include "mednafen/pce_fast/psg.h" #include "mednafen/pce_fast/input.h" #include "mednafen/pce_fast/huc.h" #include "mednafen/pce_fast/pcecd.h" #include "mednafen/pce_fast/pcecd_drive.h" #include "mednafen/hw_misc/arcade_card/arcade_card.h" #include "mednafen/mempatcher.h" #include "mednafen/cdrom/cdromif.h" #include "mednafen/cdrom/CDUtility.h" #ifdef _MSC_VER #include "mednafen/msvc_compat.h" #endif static bool old_cdimagecache = false; extern MDFNGI EmulatedPCE_Fast; MDFNGI *MDFNGameInfo = &EmulatedPCE_Fast; /* Mednafen - Multi-system Emulator * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ extern "C" unsigned long crc32(unsigned long crc, const unsigned char *buf, unsigned int len); static PCEFast_PSG *psg = NULL; extern ArcadeCard *arcade_card; // Bah, lousy globals. static Blip_Buffer sbuf[2]; bool PCE_ACEnabled; int pce_overclocked; // Statically allocated for speed...or something. uint8 ROMSpace[0x88 * 8192 + 8192]; // + 8192 for PC-as-pointer safety padding uint8 BaseRAM[32768 + 8192]; // 8KB for PCE, 32KB for Super Grafx // + 8192 for PC-as-pointer safety padding uint8 PCEIODataBuffer; readfunc PCERead[0x100]; writefunc PCEWrite[0x100]; static DECLFR(PCEBusRead) { //printf("BUS Read: %02x %04x\n", A >> 13, A); return(0xFF); } static DECLFW(PCENullWrite) { //printf("Null Write: %02x, %08x %02x\n", A >> 13, A, V); } static DECLFR(BaseRAMRead) { return((BaseRAM - (0xF8 * 8192))[A]); } static DECLFR(BaseRAMRead_Mirrored) { return(BaseRAM[A & 0x1FFF]); } static DECLFW(BaseRAMWrite) { (BaseRAM - (0xF8 * 8192))[A] = V; } static DECLFW(BaseRAMWrite_Mirrored) { BaseRAM[A & 0x1FFF] = V; } static DECLFR(IORead) { #include "mednafen/pce_fast/ioread.inc" } static DECLFW(IOWrite) { A &= 0x1FFF; switch(A >> 10) { case 0: HuC6280_StealCycle(); VDC_Write(A, V); break; case 1: HuC6280_StealCycle(); VCE_Write(A, V); break; case 2: PCEIODataBuffer = V; psg->Write(HuCPU.timestamp / pce_overclocked, A, V); break; case 3: PCEIODataBuffer = V; HuC6280_TimerWrite(A, V); break; case 4: PCEIODataBuffer = V; INPUT_Write(A, V); break; case 5: PCEIODataBuffer = V; HuC6280_IRQStatusWrite(A, V); break; case 6: if(!PCE_IsCD) break; if((A & 0x1E00) == 0x1A00) { if(arcade_card) arcade_card->Write(A & 0x1FFF, V); } else { PCECD_Write(HuCPU.timestamp * 3, A, V); } break; case 7: break; // Expansion. } } static void PCECDIRQCB(bool asserted) { if(asserted) HuC6280_IRQBegin(MDFN_IQIRQ2); else HuC6280_IRQEnd(MDFN_IQIRQ2); } bool PCE_InitCD(void) { PCECD_Settings cd_settings; memset(&cd_settings, 0, sizeof(PCECD_Settings)); cd_settings.CDDA_Volume = (double)MDFN_GetSettingUI("pce_fast.cddavolume") / 100; cd_settings.CD_Speed = MDFN_GetSettingUI("pce_fast.cdspeed"); cd_settings.ADPCM_Volume = (double)MDFN_GetSettingUI("pce_fast.adpcmvolume") / 100; cd_settings.ADPCM_LPF = MDFN_GetSettingB("pce_fast.adpcmlp"); if(cd_settings.CDDA_Volume != 1.0) MDFN_printf(_("CD-DA Volume: %d%%\n"), (int)(100 * cd_settings.CDDA_Volume)); if(cd_settings.ADPCM_Volume != 1.0) MDFN_printf(_("ADPCM Volume: %d%%\n"), (int)(100 * cd_settings.ADPCM_Volume)); return(PCECD_Init(&cd_settings, PCECDIRQCB, PCE_MASTER_CLOCK, pce_overclocked, &sbuf[0], &sbuf[1])); } static int LoadCommon(void); static void LoadCommonPre(void); static bool TestMagic(const char *name, MDFNFILE *fp) { return(TRUE); } static int Load(const char *name, MDFNFILE *fp) { uint32 headerlen = 0; uint32 r_size; LoadCommonPre(); { if(GET_FSIZE_PTR(fp) & 0x200) // 512 byte header! headerlen = 512; } r_size = GET_FSIZE_PTR(fp) - headerlen; if(r_size > 4096 * 1024) r_size = 4096 * 1024; for(int x = 0; x < 0x100; x++) { PCERead[x] = PCEBusRead; PCEWrite[x] = PCENullWrite; } uint32 crc = crc32(0, GET_FDATA_PTR(fp) + headerlen, GET_FSIZE_PTR(fp) - headerlen); HuCLoad(GET_FDATA_PTR(fp) + headerlen, GET_FSIZE_PTR(fp) - headerlen, crc); return(LoadCommon()); } static void LoadCommonPre(void) { // FIXME: Make these globals less global! pce_overclocked = MDFN_GetSettingUI("pce_fast.ocmultiplier"); PCE_ACEnabled = MDFN_GetSettingB("pce_fast.arcadecard"); if(pce_overclocked > 1) MDFN_printf(_("CPU overclock: %dx\n"), pce_overclocked); if(MDFN_GetSettingUI("pce_fast.cdspeed") > 1) MDFN_printf(_("CD-ROM speed: %ux\n"), (unsigned int)MDFN_GetSettingUI("pce_fast.cdspeed")); memset(HuCPUFastMap, 0, sizeof(HuCPUFastMap)); for(int x = 0; x < 0x100; x++) { PCERead[x] = PCEBusRead; PCEWrite[x] = PCENullWrite; } MDFNMP_Init(1024, (1 << 21) / 1024); } static int LoadCommon(void) { VDC_Init(false); { PCERead[0xF8] = BaseRAMRead; PCERead[0xF9] = PCERead[0xFA] = PCERead[0xFB] = BaseRAMRead_Mirrored; PCEWrite[0xF8] = BaseRAMWrite; PCEWrite[0xF9] = PCEWrite[0xFA] = PCEWrite[0xFB] = BaseRAMWrite_Mirrored; for(int x = 0xf8; x < 0xfb; x++) HuCPUFastMap[x] = BaseRAM - x * 8192; PCERead[0xFF] = IORead; } MDFNMP_AddRAM(8192, 0xf8 * 8192, BaseRAM); PCEWrite[0xFF] = IOWrite; HuC6280_Init(); psg = new PCEFast_PSG(&sbuf[0], &sbuf[1]); psg->SetVolume(1.0); if(PCE_IsCD) { unsigned int cdpsgvolume = MDFN_GetSettingUI("pce_fast.cdpsgvolume"); if(cdpsgvolume != 100) { MDFN_printf(_("CD PSG Volume: %d%%\n"), cdpsgvolume); } psg->SetVolume(0.678 * cdpsgvolume / 100); } PCEINPUT_Init(); PCE_Power(); MDFNGameInfo->LayerNames = "Background\0Sprites\0"; MDFNGameInfo->fps = (uint32)((double)7159090.90909090 / 455 / 263 * 65536 * 256); // Clean this up: if(!MDFN_GetSettingB("pce_fast.correct_aspect")) MDFNGameInfo->fb_width = 682; MDFNGameInfo->nominal_width = MDFN_GetSettingB("pce_fast.correct_aspect") ? 288 : 341; MDFNGameInfo->nominal_height = MDFN_GetSettingUI("pce_fast.slend") - MDFN_GetSettingUI("pce_fast.slstart") + 1; MDFNGameInfo->lcm_width = MDFN_GetSettingB("pce_fast.correct_aspect") ? 1024 : 341; MDFNGameInfo->lcm_height = MDFNGameInfo->nominal_height; return(1); } static bool TestMagicCD(std::vector *CDInterfaces) { static const uint8 magic_test[0x20] = { 0x82, 0xB1, 0x82, 0xCC, 0x83, 0x76, 0x83, 0x8D, 0x83, 0x4F, 0x83, 0x89, 0x83, 0x80, 0x82, 0xCC, 0x92, 0x98, 0x8D, 0xEC, 0x8C, 0xA0, 0x82, 0xCD, 0x8A, 0x94, 0x8E, 0xAE, 0x89, 0xEF, 0x8E, 0xD0 }; uint8 sector_buffer[2048]; CDIF *cdiface = (*CDInterfaces)[0]; CDUtility::TOC toc; bool ret = FALSE; memset(sector_buffer, 0, sizeof(sector_buffer)); cdiface->ReadTOC(&toc); for(int32 track = toc.first_track; track <= toc.last_track; track++) { if(toc.tracks[track].control & 0x4) { cdiface->ReadSector(sector_buffer, toc.tracks[track].lba, 1); if(!memcmp((char*)sector_buffer, (char *)magic_test, 0x20)) ret = TRUE; // PCE CD BIOS apparently only looks at the first data track. break; } } // If it's a PC-FX CD(Battle Heat), return false. // This is very kludgy. for(int32 track = toc.first_track; track <= toc.last_track; track++) { if(toc.tracks[track].control & 0x4) { cdiface->ReadSector(sector_buffer, toc.tracks[track].lba, 1); if(!strncmp("PC-FX:Hu_CD-ROM", (char*)sector_buffer, strlen("PC-FX:Hu_CD-ROM"))) { return(false); } } } // Now, test for the Games Express CD games. The GE BIOS seems to always look at sector 0x10, but only if the first track is a // data track. if(toc.first_track == 1 && (toc.tracks[1].control & 0x4)) { if(cdiface->ReadSector(sector_buffer, 0x10, 1)) { if(!memcmp((char *)sector_buffer + 0x8, "HACKER CD ROM SYSTEM", 0x14)) { ret = TRUE; } } } return(ret); } static int LoadCD(std::vector *CDInterfaces) { std::string bios_path = MDFN_MakeFName(MDFNMKF_FIRMWARE, 0, MDFN_GetSettingS("pce_fast.cdbios").c_str() ); LoadCommonPre(); if(!HuCLoadCD(bios_path.c_str())) return(0); PCECD_Drive_SetDisc(true, NULL, true); PCECD_Drive_SetDisc(false, (*CDInterfaces)[0], true); return(LoadCommon()); } static void Cleanup_PCE(void) { HuC_Close(); VDC_Close(); if(psg) delete psg; psg = NULL; } static void CloseGame(void) { Cleanup_PCE(); } static void Emulate(EmulateSpecStruct *espec) { INPUT_Frame(); MDFNMP_ApplyPeriodicCheats(); #if 0 { static bool firstcat = true; MDFN_PixelFormat nf; nf.bpp = 16; nf.colorspace = MDFN_COLORSPACE_RGB; nf.Rshift = 11; nf.Gshift = 5; nf.Bshift = 0; nf.Ashift = 16; nf.Rprec = 5; nf.Gprec = 6; nf.Bprec = 5; nf.Aprec = 8; espec->surface->SetFormat(nf, false); espec->VideoFormatChanged = firstcat; firstcat = false; } #endif if(espec->SoundFormatChanged) { for(int y = 0; y < 2; y++) { sbuf[y].set_sample_rate(espec->SoundRate ? espec->SoundRate : 44100, 50); sbuf[y].clock_rate((long)(PCE_MASTER_CLOCK / 3)); sbuf[y].bass_freq(10); } } VDC_RunFrame(espec, false); if(PCE_IsCD) { PCECD_Run(HuCPU.timestamp * 3); } psg->EndFrame(HuCPU.timestamp / pce_overclocked); if(espec->SoundBuf) { for(int y = 0; y < 2; y++) { sbuf[y].end_frame(HuCPU.timestamp / pce_overclocked); espec->SoundBufSize = sbuf[y].read_samples(espec->SoundBuf + y, espec->SoundBufMaxSize, 1); } } espec->MasterCycles = HuCPU.timestamp * 3; INPUT_FixTS(); HuC6280_ResetTS(); if(PCE_IsCD) PCECD_ResetTS(); } static int StateAction(StateMem *sm, int load, int data_only) { SFORMAT StateRegs[] = { SFARRAY(BaseRAM, 8192), SFVAR(PCEIODataBuffer), SFEND }; //for(int i = 8192; i < 32768; i++) // if(BaseRAM[i] != 0xFF) // printf("%d %02x\n", i, BaseRAM[i]); int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, "MAIN"); ret &= HuC6280_StateAction(sm, load, data_only); ret &= VDC_StateAction(sm, load, data_only); ret &= psg->StateAction(sm, load, data_only); ret &= INPUT_StateAction(sm, load, data_only); ret &= HuC_StateAction(sm, load, data_only); if(load) { } return(ret); } void PCE_Power(void) { memset(BaseRAM, 0x00, sizeof(BaseRAM)); for(int i = 8192; i < 32768; i++) BaseRAM[i] = 0xFF; PCEIODataBuffer = 0xFF; HuC6280_Power(); VDC_Power(); psg->Power(HuCPU.timestamp / pce_overclocked); HuC_Power(); if(PCE_IsCD) { PCECD_Power(HuCPU.timestamp * 3); } } static void DoSimpleCommand(int cmd) { switch(cmd) { case MDFN_MSC_RESET: PCE_Power(); break; case MDFN_MSC_POWER: PCE_Power(); break; } } static MDFNSetting PCESettings[] = { { "pce_fast.correct_aspect", MDFNSF_CAT_VIDEO, gettext_noop("Correct the aspect ratio."), NULL, MDFNST_BOOL, "1" }, { "pce_fast.slstart", MDFNSF_NOFLAGS, gettext_noop("First rendered scanline."), NULL, MDFNST_UINT, "4", "0", "239" }, { "pce_fast.slend", MDFNSF_NOFLAGS, gettext_noop("Last rendered scanline."), NULL, MDFNST_UINT, "235", "0", "239" }, { "pce_fast.mouse_sensitivity", MDFNSF_NOFLAGS, gettext_noop("Mouse sensitivity."), NULL, MDFNST_FLOAT, "0.50", NULL, NULL, NULL, PCEINPUT_SettingChanged }, { "pce_fast.disable_softreset", MDFNSF_NOFLAGS, gettext_noop("If set, when RUN+SEL are pressed simultaneously, disable both buttons temporarily."), NULL, MDFNST_BOOL, "0", NULL, NULL, NULL, PCEINPUT_SettingChanged }, { "pce_fast.arcadecard", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("Enable Arcade Card emulation."), NULL, MDFNST_BOOL, "1" }, { "pce_fast.ocmultiplier", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("CPU overclock multiplier."), NULL, MDFNST_UINT, "1", "1", "100"}, { "pce_fast.cdspeed", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("CD-ROM data transfer speed multiplier."), NULL, MDFNST_UINT, "1", "1", "100" }, { "pce_fast.nospritelimit", MDFNSF_NOFLAGS, gettext_noop("Remove 16-sprites-per-scanline hardware limit."), NULL, MDFNST_BOOL, "0" }, { "pce_fast.cdbios", MDFNSF_EMU_STATE, gettext_noop("Path to the CD BIOS"), NULL, MDFNST_STRING, "syscard3.pce" }, { "pce_fast.adpcmlp", MDFNSF_NOFLAGS, gettext_noop("Enable dynamic ADPCM lowpass filter."), NULL, MDFNST_BOOL, "0" }, { "pce_fast.cdpsgvolume", MDFNSF_NOFLAGS, gettext_noop("PSG volume when playing a CD game."), NULL, MDFNST_UINT, "100", "0", "200" }, { "pce_fast.cddavolume", MDFNSF_NOFLAGS, gettext_noop("CD-DA volume."), NULL, MDFNST_UINT, "100", "0", "200" }, { "pce_fast.adpcmvolume", MDFNSF_NOFLAGS, gettext_noop("ADPCM volume."), NULL, MDFNST_UINT, "100", "0", "200" }, { NULL } }; static uint8 MemRead(uint32 addr) { return(PCERead[(addr / 8192) & 0xFF](addr)); } static const FileExtensionSpecStruct KnownExtensions[] = { { ".pce", gettext_noop("PC Engine ROM Image") }, { NULL, NULL } }; static const uint8 BRAM_Init_String[8] = { 'H', 'U', 'B', 'M', 0x00, 0x88, 0x10, 0x80 }; //"HUBM\x00\x88\x10\x80"; ArcadeCard *arcade_card = NULL; static uint8 *HuCROM = NULL; static bool IsPopulous; bool PCE_IsCD; uint8 SaveRAM[2048]; static DECLFW(ACPhysWrite) { arcade_card->PhysWrite(A, V); } static DECLFR(ACPhysRead) { return(arcade_card->PhysRead(A)); } static DECLFR(SaveRAMRead) { if((!PCE_IsCD || PCECD_IsBRAMEnabled()) && (A & 8191) < 2048) return(SaveRAM[A & 2047]); else return(0xFF); } static DECLFW(SaveRAMWrite) { if((!PCE_IsCD || PCECD_IsBRAMEnabled()) && (A & 8191) < 2048) SaveRAM[A & 2047] = V; } static DECLFR(HuCRead) { return ROMSpace[A]; } static DECLFW(HuCRAMWrite) { ROMSpace[A] = V; } static DECLFW(HuCRAMWriteCDSpecial) // Hyper Dyne Special hack { BaseRAM[0x2000 | (A & 0x1FFF)] = V; ROMSpace[A] = V; } static uint8 HuCSF2Latch = 0; static DECLFR(HuCSF2Read) { return(HuCROM[(A & 0x7FFFF) + 0x80000 + HuCSF2Latch * 0x80000 ]); // | (HuCSF2Latch << 19) ]); } static DECLFW(HuCSF2Write) { if((A & 0x1FFC) == 0x1FF0) { HuCSF2Latch = (A & 0x3); } } static void Cleanup(void) { if(arcade_card) delete arcade_card; arcade_card = NULL; if(PCE_IsCD) PCECD_Close(); if(HuCROM) free(HuCROM); HuCROM = NULL; } int HuCLoad(const uint8 *data, uint32 len, uint32 crc32) { uint32 sf2_threshold = 2048 * 1024; uint32 sf2_required_size = 2048 * 1024 + 512 * 1024; uint32 m_len = (len + 8191)&~8191; bool sf2_mapper = FALSE; if(m_len >= sf2_threshold) { sf2_mapper = TRUE; if(m_len != sf2_required_size) m_len = sf2_required_size; } IsPopulous = 0; PCE_IsCD = 0; MDFN_printf(_("ROM: %dKiB\n"), (len + 1023) / 1024); MDFN_printf(_("ROM CRC32: 0x%04x\n"), crc32); if(!(HuCROM = (uint8 *)malloc(m_len))) { return(0); } memset(HuCROM, 0xFF, m_len); memcpy(HuCROM, data, (m_len < len) ? m_len : len); memset(ROMSpace, 0xFF, 0x88 * 8192 + 8192); if(m_len == 0x60000) { memcpy(ROMSpace + 0x00 * 8192, HuCROM, 0x20 * 8192); memcpy(ROMSpace + 0x20 * 8192, HuCROM, 0x20 * 8192); memcpy(ROMSpace + 0x40 * 8192, HuCROM + 0x20 * 8192, 0x10 * 8192); memcpy(ROMSpace + 0x50 * 8192, HuCROM + 0x20 * 8192, 0x10 * 8192); memcpy(ROMSpace + 0x60 * 8192, HuCROM + 0x20 * 8192, 0x10 * 8192); memcpy(ROMSpace + 0x70 * 8192, HuCROM + 0x20 * 8192, 0x10 * 8192); } else if(m_len == 0x80000) { memcpy(ROMSpace + 0x00 * 8192, HuCROM, 0x40 * 8192); memcpy(ROMSpace + 0x40 * 8192, HuCROM + 0x20 * 8192, 0x20 * 8192); memcpy(ROMSpace + 0x60 * 8192, HuCROM + 0x20 * 8192, 0x20 * 8192); } else { memcpy(ROMSpace + 0x00 * 8192, HuCROM, (m_len < 1024 * 1024) ? m_len : 1024 * 1024); } for(int x = 0x00; x < 0x80; x++) { HuCPUFastMap[x] = ROMSpace; PCERead[x] = HuCRead; } if(!memcmp(HuCROM + 0x1F26, "POPULOUS", strlen("POPULOUS"))) { uint8 *PopRAM = ROMSpace + 0x40 * 8192; memset(PopRAM, 0xFF, 32768); IsPopulous = 1; MDFN_printf("Populous\n"); for(int x = 0x40; x < 0x44; x++) { HuCPUFastMap[x] = &PopRAM[(x & 3) * 8192] - x * 8192; PCERead[x] = HuCRead; PCEWrite[x] = HuCRAMWrite; } MDFNMP_AddRAM(32768, 0x40 * 8192, PopRAM); } else { memset(SaveRAM, 0x00, 2048); memcpy(SaveRAM, BRAM_Init_String, 8); // So users don't have to manually intialize the file cabinet // in the CD BIOS screen. PCEWrite[0xF7] = SaveRAMWrite; PCERead[0xF7] = SaveRAMRead; MDFNMP_AddRAM(2048, 0xF7 * 8192, SaveRAM); } // 0x1A558 //if(len >= 0x20000 && !memcmp(HuCROM + 0x1A558, "STREET FIGHTER#", strlen("STREET FIGHTER#"))) if(sf2_mapper) { for(int x = 0x40; x < 0x80; x++) { // FIXME: PCE_FAST HuCPUFastMap[x] = NULL; // Make sure our reads go through our read function, and not a table lookup PCERead[x] = HuCSF2Read; } PCEWrite[0] = HuCSF2Write; MDFN_printf("Street Fighter 2 Mapper\n"); HuCSF2Latch = 0; } return(1); } bool IsBRAMUsed(void) { if(memcmp(SaveRAM, BRAM_Init_String, 8)) // HUBM string is modified/missing return(1); for(int x = 8; x < 2048; x++) if(SaveRAM[x]) return(1); return(0); } int HuCLoadCD(const char *bios_path) { static const FileExtensionSpecStruct KnownBIOSExtensions[] = { { ".pce", gettext_noop("PC Engine ROM Image") }, { ".bin", gettext_noop("PC Engine ROM Image") }, { ".bios", gettext_noop("BIOS Image") }, { NULL, NULL } }; MDFNFILE fp; if(!fp.Open(bios_path, KnownBIOSExtensions, _("CD BIOS"))) { return(0); } memset(ROMSpace, 0xFF, 262144); memcpy(ROMSpace, GET_FDATA(fp) + (GET_FSIZE(fp) & 512), ((GET_FSIZE(fp) & ~512) > 262144) ? 262144 : (GET_FSIZE(fp) &~ 512) ); fp.Close(); PCE_IsCD = 1; PCE_InitCD(); MDFN_printf(_("Arcade Card Emulation: %s\n"), PCE_ACEnabled ? _("Enabled") : _("Disabled")); for(int x = 0; x < 0x40; x++) { HuCPUFastMap[x] = ROMSpace; PCERead[x] = HuCRead; } for(int x = 0x68; x < 0x88; x++) { HuCPUFastMap[x] = ROMSpace; PCERead[x] = HuCRead; PCEWrite[x] = HuCRAMWrite; } PCEWrite[0x80] = HuCRAMWriteCDSpecial; // Hyper Dyne Special hack MDFNMP_AddRAM(262144, 0x68 * 8192, ROMSpace + 0x68 * 8192); if(PCE_ACEnabled) { if (!(arcade_card = new ArcadeCard())) { MDFN_PrintError(_("Error creating %s object.\n"), "ArcadeCard"); Cleanup(); return(0); } for(int x = 0x40; x < 0x44; x++) { HuCPUFastMap[x] = NULL; PCERead[x] = ACPhysRead; PCEWrite[x] = ACPhysWrite; } } memset(SaveRAM, 0x00, 2048); memcpy(SaveRAM, BRAM_Init_String, 8); // So users don't have to manually intialize the file cabinet // in the CD BIOS screen. PCEWrite[0xF7] = SaveRAMWrite; PCERead[0xF7] = SaveRAMRead; MDFNMP_AddRAM(2048, 0xF7 * 8192, SaveRAM); return(1); } int HuC_StateAction(StateMem *sm, int load, int data_only) { SFORMAT StateRegs[] = { SFARRAY(ROMSpace + 0x40 * 8192, IsPopulous ? 32768 : 0), SFARRAY(SaveRAM, IsPopulous ? 0 : 2048), SFARRAY(ROMSpace + 0x68 * 8192, PCE_IsCD ? 262144 : 0), SFVAR(HuCSF2Latch), SFEND }; int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, "HuC"); if(load) HuCSF2Latch &= 0x3; if(PCE_IsCD) { ret &= PCECD_StateAction(sm, load, data_only); if(arcade_card) ret &= arcade_card->StateAction(sm, load, data_only); } return(ret); } void HuC_Close(void) { Cleanup(); } void HuC_Power(void) { if(PCE_IsCD) memset(ROMSpace + 0x68 * 8192, 0x00, 262144); if(arcade_card) arcade_card->Power(); } MDFNGI EmulatedPCE_Fast = { "pce_fast", "PC Engine (CD)/TurboGrafx 16 (CD)/SuperGrafx", KnownExtensions, MODPRIO_INTERNAL_LOW, NULL, &PCEInputInfo, Load, TestMagic, LoadCD, TestMagicCD, CloseGame, VDC_SetLayerEnableMask, NULL, NULL, NULL, NULL, NULL, MemRead, NULL, false, StateAction, Emulate, PCEINPUT_SetInput, DoSimpleCommand, PCESettings, MDFN_MASTERCLOCK_FIXED(PCE_MASTER_CLOCK), 0, true, // Multires possible? 0, // lcm_width 0, // lcm_height NULL, // Dummy 288, // Nominal width 232, // Nominal height 512, // Framebuffer width 242, // Framebuffer height 2, // Number of output sound channels }; #ifdef NEED_CD static void ReadM3U(std::vector &file_list, std::string path, unsigned depth = 0) { std::vector ret; FileWrapper m3u_file(path.c_str(), FileWrapper::MODE_READ, _("M3U CD Set")); std::string dir_path; char linebuf[2048]; MDFN_GetFilePathComponents(path, &dir_path); while(m3u_file.get_line(linebuf, sizeof(linebuf))) { std::string efp; if(linebuf[0] == '#') continue; MDFN_rtrim(linebuf); if(linebuf[0] == 0) continue; efp = MDFN_EvalFIP(dir_path, std::string(linebuf)); if(efp.size() >= 4 && efp.substr(efp.size() - 4) == ".m3u") { if(efp == path) throw(MDFN_Error(0, _("M3U at \"%s\" references self."), efp.c_str())); if(depth == 99) throw(MDFN_Error(0, _("M3U load recursion too deep!"))); ReadM3U(file_list, efp, depth++); } else file_list.push_back(efp); } } #ifdef NEED_CD static std::vector CDInterfaces; // FIXME: Cleanup on error out. #endif // TODO: LoadCommon() MDFNGI *MDFNI_LoadCD(const char *force_module, const char *devicename) { MDFN_printf(_("Loading %s...\n\n"), devicename ? devicename : _("PHYSICAL CD")); try { if(devicename && strlen(devicename) > 4 && !strcasecmp(devicename + strlen(devicename) - 4, ".m3u")) { std::vector file_list; ReadM3U(file_list, devicename); for(unsigned i = 0; i < file_list.size(); i++) { CDInterfaces.push_back(CDIF_Open(file_list[i].c_str(), false, old_cdimagecache)); } } else { CDInterfaces.push_back(CDIF_Open(devicename, false, old_cdimagecache)); } } catch(std::exception &e) { MDFND_PrintError(e.what()); MDFN_PrintError(_("Error opening CD.")); return(0); } // // Print out a track list for all discs. // MDFN_indent(1); for(unsigned i = 0; i < CDInterfaces.size(); i++) { CDUtility::TOC toc; CDInterfaces[i]->ReadTOC(&toc); MDFN_printf(_("CD %d Layout:\n"), i + 1); MDFN_indent(1); for(int32 track = toc.first_track; track <= toc.last_track; track++) { MDFN_printf(_("Track %2d, LBA: %6d %s\n"), track, toc.tracks[track].lba, (toc.tracks[track].control & 0x4) ? "DATA" : "AUDIO"); } MDFN_printf("Leadout: %6d\n", toc.tracks[100].lba); MDFN_indent(-1); MDFN_printf("\n"); } MDFN_indent(-1); // This if statement will be true if force_module references a system without CDROM support. if(!MDFNGameInfo->LoadCD) { MDFN_PrintError(_("Specified system \"%s\" doesn't support CDs!"), force_module); return(0); } MDFN_printf(_("Using module: %s(%s)\n\n"), MDFNGameInfo->shortname, MDFNGameInfo->fullname); if(!(MDFNGameInfo->LoadCD(&CDInterfaces))) { for(unsigned i = 0; i < CDInterfaces.size(); i++) delete CDInterfaces[i]; CDInterfaces.clear(); MDFNGameInfo = NULL; return(0); } //MDFNI_SetLayerEnableMask(~0ULL); MDFN_LoadGameCheats(NULL); MDFNMP_InstallReadPatches(); return(MDFNGameInfo); } #endif MDFNGI *MDFNI_LoadGame(const char *force_module, const char *name) { MDFNFILE GameFile; std::vector valid_iae; MDFNGameInfo = &EmulatedPCE_Fast; #ifdef NEED_CD if(strlen(name) > 4 && (!strcasecmp(name + strlen(name) - 4, ".cue") || !strcasecmp(name + strlen(name) - 4, ".ccd") || !strcasecmp(name + strlen(name) - 4, ".toc") || !strcasecmp(name + strlen(name) - 4, ".m3u"))) return(MDFNI_LoadCD(force_module, name)); #endif MDFN_printf(_("Loading %s...\n"),name); MDFN_indent(1); // Construct a NULL-delimited list of known file extensions for MDFN_fopen() const FileExtensionSpecStruct *curexts = MDFNGameInfo->FileExtensions; while(curexts->extension && curexts->description) { valid_iae.push_back(*curexts); curexts++; } if(!GameFile.Open(name, &valid_iae[0], _("game"))) { MDFNGameInfo = NULL; return 0; } MDFN_printf(_("Using module: %s(%s)\n\n"), MDFNGameInfo->shortname, MDFNGameInfo->fullname); MDFN_indent(1); // // Load per-game settings // // Maybe we should make a "pgcfg" subdir, and automatically load all files in it? // End load per-game settings // if(MDFNGameInfo->Load(name, &GameFile) <= 0) { GameFile.Close(); MDFN_indent(-2); MDFNGameInfo = NULL; return(0); } MDFN_LoadGameCheats(NULL); MDFNMP_InstallReadPatches(); MDFN_indent(-2); if(!MDFNGameInfo->name) { unsigned int x; char *tmp; MDFNGameInfo->name = (UTF8 *)strdup(GetFNComponent(name)); for(x=0;xname);x++) { if(MDFNGameInfo->name[x] == '_') MDFNGameInfo->name[x] = ' '; } if((tmp = strrchr((char *)MDFNGameInfo->name, '.'))) *tmp = 0; } return(MDFNGameInfo); } static int curindent = 0; void MDFN_indent(int indent) { curindent += indent; } static uint8 lastchar = 0; void MDFN_printf(const char *format, ...) { char *format_temp; char *temp; unsigned int x, newlen; va_list ap; va_start(ap,format); // First, determine how large our format_temp buffer needs to be. uint8 lastchar_backup = lastchar; // Save lastchar! for(newlen=x=0;xpath); check_variables(); game = MDFNI_LoadGame(MEDNAFEN_CORE_NAME_MODULE, info->path); if (!game) return false; surf = (MDFN_Surface*)calloc(1, sizeof(*surf)); if (!surf) return false; surf->width = FB_WIDTH; surf->height = FB_HEIGHT; surf->pitch = FB_WIDTH; surf->pixels = (uint16_t*)calloc(1, FB_WIDTH * FB_HEIGHT * 3); if (!surf->pixels) { free(surf); return false; } // Possible endian bug ... for (unsigned i = 0; i < MAX_PLAYERS; i++) PCEINPUT_SetInput(i, "gamepad", &input_buf[i][0]); VDC_SetPixelFormat(); return game; } void retro_unload_game(void) { if(!MDFNGameInfo) return; MDFN_FlushGameCheats(0); MDFNGameInfo->CloseGame(); if(MDFNGameInfo->name) free(MDFNGameInfo->name); MDFNGameInfo->name = NULL; MDFNMP_Kill(); MDFNGameInfo = NULL; #ifdef NEED_CD for(unsigned i = 0; i < CDInterfaces.size(); i++) delete CDInterfaces[i]; CDInterfaces.clear(); #endif } static void update_input(void) { static unsigned map[] = { RETRO_DEVICE_ID_JOYPAD_A, RETRO_DEVICE_ID_JOYPAD_B, RETRO_DEVICE_ID_JOYPAD_SELECT, RETRO_DEVICE_ID_JOYPAD_START, RETRO_DEVICE_ID_JOYPAD_UP, RETRO_DEVICE_ID_JOYPAD_RIGHT, RETRO_DEVICE_ID_JOYPAD_DOWN, RETRO_DEVICE_ID_JOYPAD_LEFT, RETRO_DEVICE_ID_JOYPAD_Y, RETRO_DEVICE_ID_JOYPAD_X, RETRO_DEVICE_ID_JOYPAD_L, RETRO_DEVICE_ID_JOYPAD_R, RETRO_DEVICE_ID_JOYPAD_L2 }; for (unsigned j = 0; j < MAX_PLAYERS; j++) { uint16_t input_state = 0; for (unsigned i = 0; i < MAX_BUTTONS; i++) input_state |= input_state_cb(j, RETRO_DEVICE_JOYPAD, 0, map[i]) ? (1 << i) : 0; // Input data must be little endian. input_buf[j][0] = (input_state >> 0) & 0xff; input_buf[j][1] = (input_state >> 8) & 0xff; } } static uint64_t video_frames, audio_frames; void retro_run(void) { MDFNGI *curgame = (MDFNGI*)game; input_poll_cb(); update_input(); static int16_t sound_buf[0x10000]; static int32_t rects[FB_MAX_HEIGHT]; rects[0] = ~0; EmulateSpecStruct spec = {0}; spec.surface = surf; spec.SoundRate = 44100; spec.SoundBuf = sound_buf; spec.LineWidths = rects; spec.SoundBufMaxSize = sizeof(sound_buf) / 2; spec.SoundVolume = 1.0; spec.soundmultiplier = 1.0; spec.SoundBufSize = 0; spec.VideoFormatChanged = false; spec.SoundFormatChanged = false; if (spec.SoundRate != last_sound_rate) { spec.SoundFormatChanged = true; last_sound_rate = spec.SoundRate; } Emulate(&spec); int16 *const SoundBuf = spec.SoundBuf + spec.SoundBufSizeALMS * curgame->soundchan; int32 SoundBufSize = spec.SoundBufSize - spec.SoundBufSizeALMS; const int32 SoundBufMaxSize = spec.SoundBufMaxSize - spec.SoundBufSizeALMS; spec.SoundBufSize = spec.SoundBufSizeALMS + SoundBufSize; unsigned width = spec.DisplayRect.w & ~0x1; unsigned height = spec.DisplayRect.h; video_cb(surf->pixels + surf->pitch * spec.DisplayRect.y, width, height, FB_WIDTH << 1); video_frames++; audio_frames += spec.SoundBufSize; audio_batch_cb(spec.SoundBuf, spec.SoundBufSize); bool updated = false; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) check_variables(); } void retro_get_system_info(struct retro_system_info *info) { memset(info, 0, sizeof(*info)); info->library_name = MEDNAFEN_CORE_NAME; info->library_version = MEDNAFEN_CORE_VERSION; info->need_fullpath = true; info->valid_extensions = MEDNAFEN_CORE_EXTENSIONS; info->block_extract = false; } void retro_get_system_av_info(struct retro_system_av_info *info) { memset(info, 0, sizeof(*info)); info->timing.fps = MEDNAFEN_CORE_TIMING_FPS; info->timing.sample_rate = 44100; info->geometry.base_width = MEDNAFEN_CORE_GEOMETRY_BASE_W; info->geometry.base_height = MEDNAFEN_CORE_GEOMETRY_BASE_H; info->geometry.max_width = MEDNAFEN_CORE_GEOMETRY_MAX_W; info->geometry.max_height = MEDNAFEN_CORE_GEOMETRY_MAX_H; info->geometry.aspect_ratio = MEDNAFEN_CORE_GEOMETRY_ASPECT_RATIO; } void retro_deinit() { if (surf) free(surf); surf = NULL; if (log_cb) { log_cb(RETRO_LOG_INFO, "[%s]: Samples / Frame: %.5f\n", MEDNAFEN_CORE_NAME, (double)audio_frames / video_frames); log_cb(RETRO_LOG_INFO, "[%s]: Estimated FPS: %.5f\n", MEDNAFEN_CORE_NAME, (double)video_frames * 44100 / audio_frames); } } unsigned retro_get_region(void) { return RETRO_REGION_NTSC; // FIXME: Regions for other cores. } unsigned retro_api_version(void) { return RETRO_API_VERSION; } void retro_set_controller_port_device(unsigned in_port, unsigned device) { MDFNGI *currgame = (MDFNGI*)game; if (!currgame) return; switch(device) { case RETRO_DEVICE_JOYPAD: PCEINPUT_SetInput(in_port, "gamepad", &input_buf[in_port][0]); break; case RETRO_DEVICE_MOUSE: PCEINPUT_SetInput(in_port, "mouse", &input_buf[in_port][0]); break; } } void retro_set_environment(retro_environment_t cb) { environ_cb = cb; static const struct retro_variable vars[] = { { "pce_fast_cdimagecache", "CD Image Cache (Restart); disabled|enabled" }, { "pce_nospritelimit", "No Sprite Limit; disabled|enabled" }, { "pce_keepaspect", "Keep Aspect; enabled|disabled" }, { "pce_initial_scanline", "Initial scanline; 3|4|5|6|7|8|9|10|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|0|1|2" }, { "pce_last_scanline", "Last scanline; 241|242|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240" }, { "pce_cddavolume", "(CD) CDDA Volume; 100|0|10|20|30|40|50|60|70|80|90" }, { "pce_adpcmvolume", "(CD) ADPCM Volume; 100|0|10|20|30|40|50|60|70|80|90" }, { "pce_cdpsgvolume", "(CD) CD PSG Volume; 100|0|10|20|30|40|50|60|70|80|90" }, { "pce_cdspeed", "(CD) CD Speed; 1|2|4|8" }, { NULL, NULL }, }; static const struct retro_controller_description pads[] = { { "PCE Joypad", RETRO_DEVICE_JOYPAD }, { "Mouse", RETRO_DEVICE_MOUSE }, }; static const struct retro_controller_info ports[] = { { pads, 2 }, { pads, 2 }, { 0 }, }; cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void*)vars); environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports); } void retro_set_audio_sample(retro_audio_sample_t cb) { audio_cb = cb; } void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) { audio_batch_cb = cb; } void retro_set_input_poll(retro_input_poll_t cb) { input_poll_cb = cb; } void retro_set_input_state(retro_input_state_t cb) { input_state_cb = cb; } void retro_set_video_refresh(retro_video_refresh_t cb) { video_cb = cb; } static size_t serialize_size; size_t retro_serialize_size(void) { StateMem st; memset(&st, 0, sizeof(st)); if (!MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL)) return 0; free(st.data); return serialize_size = st.len; } bool retro_serialize(void *data, size_t size) { StateMem st; memset(&st, 0, sizeof(st)); st.data = (uint8_t*)data; st.malloced = size; return MDFNSS_SaveSM(&st, 0, 0, NULL, NULL, NULL); } bool retro_unserialize(const void *data, size_t size) { StateMem st; memset(&st, 0, sizeof(st)); st.data = (uint8_t*)data; st.len = size; return MDFNSS_LoadSM(&st, 0, 0); } void *retro_get_memory_data(unsigned type) { switch (type) { case RETRO_MEMORY_SAVE_RAM: if (IsPopulous) return (uint8_t*)(ROMSpace + 0x40 * 8192); return (uint8_t*)SaveRAM; default: break; } return NULL; } size_t retro_get_memory_size(unsigned type) { switch (type) { case RETRO_MEMORY_SAVE_RAM: if (IsPopulous) return 32768; return 2048; default: break; } return 0; } void retro_cheat_reset(void) {} void retro_cheat_set(unsigned, bool, const char *) {} MDFN_Thread *MDFND_CreateThread(int (*fn)(void *), void *data) { return (MDFN_Thread*)sthread_create((void (*)(void*))fn, data); } void MDFND_WaitThread(MDFN_Thread *thr, int *val) { sthread_join((sthread_t*)thr); if (val) { *val = 0; fprintf(stderr, "WaitThread relies on return value.\n"); } } void MDFND_KillThread(MDFN_Thread *) { fprintf(stderr, "Killing a thread is a BAD IDEA!\n"); } MDFN_Mutex *MDFND_CreateMutex(void) { return (MDFN_Mutex*)slock_new(); } void MDFND_DestroyMutex(MDFN_Mutex *lock) { slock_free((slock_t*)lock); } int MDFND_LockMutex(MDFN_Mutex *lock) { slock_lock((slock_t*)lock); return 0; } int MDFND_UnlockMutex(MDFN_Mutex *lock) { slock_unlock((slock_t*)lock); return 0; } MDFN_Cond *MDFND_CreateCond(void) { return (MDFN_Cond*)scond_new(); } void MDFND_DestroyCond(MDFN_Cond *cond) { scond_free((scond_t*)cond); } int MDFND_WaitCond(MDFN_Cond *cond, MDFN_Mutex *mutex) { scond_wait((scond_t*)cond, (slock_t*)mutex); return 0; // not sure about this return } int MDFND_SignalCond(MDFN_Cond *cond) { scond_signal((scond_t*)cond); return 0; // not sure about this return } #ifdef _WIN32 static void sanitize_path(std::string &path) { size_t size = path.size(); for (size_t i = 0; i < size; i++) if (path[i] == '/') path[i] = '\\'; } #endif // Use a simpler approach to make sure that things go right for libretro. std::string MDFN_MakeFName(MakeFName_Type type, int id1, const char *cd1) { char slash; #ifdef _WIN32 slash = '\\'; #else slash = '/'; #endif std::string ret; switch (type) { case MDFNMKF_SAV: ret = retro_save_directory +slash + retro_base_name + std::string(".") + std::string(cd1); break; case MDFNMKF_FIRMWARE: ret = retro_base_directory + slash + std::string(cd1); #ifdef _WIN32 sanitize_path(ret); // Because Windows path handling is mongoloid. #endif break; default: break; } if (log_cb) log_cb(RETRO_LOG_INFO, "MDFN_MakeFName: %s\n", ret.c_str()); return ret; } void MDFND_Message(const char *str) { if (log_cb) log_cb(RETRO_LOG_INFO, "%s\n", str); } void MDFND_MidSync(const EmulateSpecStruct *) {} void MDFN_MidLineUpdate(EmulateSpecStruct *espec, int y) { //MDFND_MidLineUpdate(espec, y); } void MDFND_PrintError(const char* err) { if (log_cb) log_cb(RETRO_LOG_ERROR, "%s\n", err); } void MDFND_Sleep(unsigned int time) { retro_sleep(time); } /* forward declarations */ extern void MDFND_DispMessage(unsigned char *str); void MDFND_DispMessage(unsigned char *str) { const char *strc = (const char*)str; struct retro_message msg = { strc, 180 }; environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &msg); } void MDFN_DispMessage(const char *format, ...) { struct retro_message msg; va_list ap; va_start(ap,format); char *str = NULL; const char *strc = NULL; trio_vasprintf(&str, format,ap); va_end(ap); strc = str; msg.frames = 180; msg.msg = strc; environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &msg); } void MDFN_ResetMessages(void) { }