1 /******************************************************************************/
2 /* Mednafen NEC PC-FX Emulation Module */
3 /******************************************************************************/
4 /* pcfx.cpp:
5 ** Copyright (C) 2006-2017 Mednafen Team
6 **
7 ** This program is free software; you can redistribute it and/or
8 ** modify it under the terms of the GNU General Public License
9 ** as published by the Free Software Foundation; either version 2
10 ** of the License, or (at your option) any later version.
11 **
12 ** This program is distributed in the hope that it will be useful,
13 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ** GNU General Public License for more details.
16 **
17 ** You should have received a copy of the GNU General Public License
18 ** along with this program; if not, write to the Free Software Foundation, Inc.,
19 ** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #include "pcfx.h"
23 #include "soundbox.h"
24 #include "input.h"
25 #include "king.h"
26 #include "timer.h"
27 #include "interrupt.h"
28 #include "debug.h"
29 #include "rainbow.h"
30 #include "huc6273.h"
31 #include "fxscsi.h"
32 #include <mednafen/cdrom/scsicd.h>
33 #include <mednafen/mempatcher.h>
34 #include <mednafen/cdrom/CDInterface.h>
35 #include <mednafen/hash/md5.h>
36 #include <mednafen/FileStream.h>
37 #include <mednafen/compress/GZFileStream.h>
38
39 #include <trio/trio.h>
40
41 extern MDFNGI EmulatedPCFX;
42
43 namespace MDFN_IEN_PCFX
44 {
45
46 /* FIXME: soundbox, vce, vdc, rainbow, and king store wait states should be 4, not 2, but V810 has write buffers which can mask wait state penalties.
47 This is a hack to somewhat address the issue, but to really fix it, we need to handle write buffer emulation in the V810 emulation core itself.
48 */
49 static std::vector<CDInterface*> *cdifs = NULL;
50
51 V810 PCFX_V810;
52
53 static uint8 *BIOSROM = NULL; // 1MB
54 static uint8 *RAM = NULL; // 2MB
55 static uint8 *FXSCSIROM = NULL; // 512KiB
56
57 static uint32 RAM_LPA; // Last page access
58
59 static const int RAM_PageSize = 2048;
60 static const int RAM_PageNOTMask = ~(RAM_PageSize - 1);
61
62 static uint16 Last_VDC_AR[2];
63
64 static bool WantHuC6273 = false;
65
66 //static
67 VDC *fx_vdc_chips[2];
68
69 static uint16 BackupControl;
70 static uint8 BackupRAM[0x8000], ExBackupRAM[0x8000];
71 static uint8 ExBusReset; // I/O Register at 0x0700
72
73 static bool BRAMDisabled; // Cached at game load, don't remove this caching behavior or save game loss may result(if we ever get a GUI).
74
75 //
76 // Set BackupSignalDirty to true on writes to BackupRAM[] and ExBackupRAM[], and on save state load.
77 // Set to false on save game load, and on periodic evaluation during emulation.
78 //
79 // Set BackupSaveDelay to 0 on save game load.
80 //
81 static bool BackupSignalDirty;
82 static uint32 BackupSaveDelay;
83
84 static void SaveBackupMemory(void);
85
86 // Checks to see if this main-RAM-area access
87 // is in the same DRAM page as the last access.
88 #define RAMLPCHECK \
89 { \
90 if((A & RAM_PageNOTMask) != RAM_LPA) \
91 { \
92 timestamp += 3; \
93 RAM_LPA = A & RAM_PageNOTMask; \
94 } \
95 }
96
97 static v810_timestamp_t next_pad_ts, next_timer_ts, next_adpcm_ts, next_king_ts;
98
PCFX_FixNonEvents(void)99 void PCFX_FixNonEvents(void)
100 {
101 if(next_pad_ts & 0x40000000)
102 next_pad_ts = PCFX_EVENT_NONONO;
103
104 if(next_timer_ts & 0x40000000)
105 next_timer_ts = PCFX_EVENT_NONONO;
106
107 if(next_adpcm_ts & 0x40000000)
108 next_adpcm_ts = PCFX_EVENT_NONONO;
109
110 if(next_king_ts & 0x40000000)
111 next_king_ts = PCFX_EVENT_NONONO;
112 }
113
PCFX_Event_Reset(void)114 void PCFX_Event_Reset(void)
115 {
116 next_pad_ts = PCFX_EVENT_NONONO;
117 next_timer_ts = PCFX_EVENT_NONONO;
118 next_adpcm_ts = PCFX_EVENT_NONONO;
119 next_king_ts = PCFX_EVENT_NONONO;
120 }
121
CalcNextTS(void)122 static INLINE uint32 CalcNextTS(void)
123 {
124 v810_timestamp_t next_timestamp = next_king_ts;
125
126 if(next_timestamp > next_pad_ts)
127 next_timestamp = next_pad_ts;
128
129 if(next_timestamp > next_timer_ts)
130 next_timestamp = next_timer_ts;
131
132 if(next_timestamp > next_adpcm_ts)
133 next_timestamp = next_adpcm_ts;
134
135 return(next_timestamp);
136 }
137
RebaseTS(const v810_timestamp_t timestamp,const v810_timestamp_t new_base_timestamp)138 static void RebaseTS(const v810_timestamp_t timestamp, const v810_timestamp_t new_base_timestamp)
139 {
140 assert(next_pad_ts > timestamp);
141 assert(next_timer_ts > timestamp);
142 assert(next_adpcm_ts > timestamp);
143 assert(next_king_ts > timestamp);
144
145 next_pad_ts -= (timestamp - new_base_timestamp);
146 next_timer_ts -= (timestamp - new_base_timestamp);
147 next_adpcm_ts -= (timestamp - new_base_timestamp);
148 next_king_ts -= (timestamp - new_base_timestamp);
149
150 //printf("RTS: %d %d %d %d\n", next_pad_ts, next_timer_ts, next_adpcm_ts, next_king_ts);
151 }
152
153
PCFX_SetEvent(const int type,const v810_timestamp_t next_timestamp)154 void PCFX_SetEvent(const int type, const v810_timestamp_t next_timestamp)
155 {
156 //assert(next_timestamp > PCFX_V810.v810_timestamp);
157
158 if(type == PCFX_EVENT_PAD)
159 next_pad_ts = next_timestamp;
160 else if(type == PCFX_EVENT_TIMER)
161 next_timer_ts = next_timestamp;
162 else if(type == PCFX_EVENT_ADPCM)
163 next_adpcm_ts = next_timestamp;
164 else if(type == PCFX_EVENT_KING)
165 next_king_ts = next_timestamp;
166
167 if(next_timestamp < PCFX_V810.GetEventNT())
168 PCFX_V810.SetEventNT(next_timestamp);
169 }
170
pcfx_event_handler(const v810_timestamp_t timestamp)171 int32 MDFN_FASTCALL pcfx_event_handler(const v810_timestamp_t timestamp)
172 {
173 if(timestamp >= next_king_ts)
174 next_king_ts = KING_Update(timestamp);
175
176 if(timestamp >= next_pad_ts)
177 next_pad_ts = FXINPUT_Update(timestamp);
178
179 if(timestamp >= next_timer_ts)
180 next_timer_ts = FXTIMER_Update(timestamp);
181
182 if(timestamp >= next_adpcm_ts)
183 next_adpcm_ts = SoundBox_ADPCMUpdate(timestamp);
184
185 #if 1
186 assert(next_king_ts > timestamp);
187 assert(next_pad_ts > timestamp);
188 assert(next_timer_ts > timestamp);
189 assert(next_adpcm_ts > timestamp);
190 #endif
191 return(CalcNextTS());
192 }
193
194 // Called externally from debug.cpp
ForceEventUpdates(const uint32 timestamp)195 void ForceEventUpdates(const uint32 timestamp)
196 {
197 next_king_ts = KING_Update(timestamp);
198 next_pad_ts = FXINPUT_Update(timestamp);
199 next_timer_ts = FXTIMER_Update(timestamp);
200 next_adpcm_ts = SoundBox_ADPCMUpdate(timestamp);
201
202 //printf("Meow: %d\n", CalcNextTS());
203 PCFX_V810.SetEventNT(CalcNextTS());
204
205 //printf("FEU: %d %d %d %d\n", next_pad_ts, next_timer_ts, next_adpcm_ts, next_king_ts);
206 }
207
208 #include "io-handler.inc"
209 #include "mem-handler.inc"
210
211 typedef struct
212 {
213 int8 tracknum;
214 int8 format;
215 uint32 lba;
216 } CDGameEntryTrack;
217
218 typedef struct
219 {
220 const char *name;
221 const char *name_original; // Original non-Romanized text.
222 const uint32 flags; // Emulation flags.
223 const unsigned int discs; // Number of discs for this game.
224 CDGameEntryTrack tracks[2][100]; // 99 tracks and 1 leadout track
225 } CDGameEntry;
226
227 #define CDGE_FORMAT_AUDIO 0
228 #define CDGE_FORMAT_DATA 1
229
230 #define CDGE_FLAG_ACCURATE_V810 0x01
231 #define CDGE_FLAG_FXGA 0x02
232
233 static uint32 EmuFlags;
234
235 static const CDGameEntry GameList[] =
236 {
237 #include "gamedb.inc"
238 };
239
240
Emulate(EmulateSpecStruct * espec)241 static void Emulate(EmulateSpecStruct *espec)
242 {
243 //printf("%d\n", PCFX_V810.v810_timestamp);
244
245 FXINPUT_Frame();
246
247 MDFNMP_ApplyPeriodicCheats();
248
249 if(espec->VideoFormatChanged)
250 KING_SetPixelFormat(espec->surface->format); //.Rshift, espec->surface->format.Gshift, espec->surface->format.Bshift);
251
252 if(espec->SoundFormatChanged)
253 SoundBox_SetSoundRate(espec->SoundRate);
254
255
256 KING_StartFrame(fx_vdc_chips, espec); //espec->surface, &espec->DisplayRect, espec->LineWidths, espec->skip);
257
258 v810_timestamp_t v810_timestamp;
259 v810_timestamp = PCFX_V810.Run(pcfx_event_handler);
260
261
262 PCFX_FixNonEvents();
263
264 // Call before resetting v810_timestamp
265 ForceEventUpdates(v810_timestamp);
266
267 //
268 // Call KING_EndFrame() before SoundBox_Flush(), otherwise CD-DA audio distortion will occur due to sound data being updated
269 // after it was needed instead of before.
270 //
271 KING_EndFrame(v810_timestamp);
272
273 //
274 // new_base_ts is guaranteed to be <= v810_timestamp
275 //
276 v810_timestamp_t new_base_ts;
277 espec->SoundBufSize = SoundBox_Flush(v810_timestamp, &new_base_ts, espec->SoundBuf, espec->SoundBufMaxSize, espec->NeedSoundReverse);
278 espec->NeedSoundReverse = false;
279
280 KING_ResetTS(new_base_ts);
281 FXTIMER_ResetTS(new_base_ts);
282 FXINPUT_ResetTS(new_base_ts);
283 SoundBox_ResetTS(new_base_ts);
284
285 // Call this AFTER all the EndFrame/Flush/ResetTS stuff
286 RebaseTS(v810_timestamp, new_base_ts);
287
288 espec->MasterCycles = v810_timestamp - new_base_ts;
289
290 PCFX_V810.ResetTS(new_base_ts);
291
292 //
293 //
294 //
295 if(BackupSignalDirty)
296 {
297 BackupSaveDelay = 120;
298 BackupSignalDirty = false;
299 }
300 else if(BackupSaveDelay)
301 {
302 BackupSaveDelay--;
303
304 if(!BackupSaveDelay)
305 {
306 //puts("SAVE");
307 try
308 {
309 SaveBackupMemory();
310 }
311 catch(std::exception &e)
312 {
313 MDFN_Notify(MDFN_NOTICE_ERROR, _("Error saving save-game memory: %s"), e.what());
314 BackupSaveDelay = 60 * 60; // Try it again in about 60 seconds emulated time(unless more writes occur to the backup memory before then, then the regular delay
315 // will be used from that time).
316 }
317 }
318 }
319 }
320
PCFX_Reset(void)321 static void PCFX_Reset(void)
322 {
323 const uint32 timestamp = PCFX_V810.v810_timestamp;
324
325 //printf("Reset: %d\n", timestamp);
326
327 // Make sure all devices are synched to current timestamp before calling their Reset()/Power()(though devices should already do this sort of thing on their
328 // own, but it's not implemented for all of them yet, and even if it was all implemented this is also INSURANCE).
329 ForceEventUpdates(timestamp);
330
331 PCFX_Event_Reset();
332
333 RAM_LPA = 0;
334
335 ExBusReset = 0;
336 BackupControl = 0;
337
338 Last_VDC_AR[0] = 0;
339 Last_VDC_AR[1] = 0;
340
341 memset(RAM, 0x00, 2048 * 1024);
342
343 for(int i = 0; i < 2; i++)
344 {
345 int32 dummy_ne MDFN_NOWARN_UNUSED;
346
347 dummy_ne = fx_vdc_chips[i]->Reset();
348 }
349
350 KING_Reset(timestamp); // SCSICD_Power() is called from KING_Reset()
351 SoundBox_Reset(timestamp);
352 RAINBOW_Reset();
353
354 if(WantHuC6273)
355 HuC6273_Reset();
356
357 PCFXIRQ_Reset();
358 FXTIMER_Reset();
359 PCFX_V810.Reset();
360
361 // Force device updates so we can get new next event timestamp values.
362 ForceEventUpdates(timestamp);
363 }
364
PCFX_Power(void)365 static void PCFX_Power(void)
366 {
367 PCFX_Reset();
368 }
369
370 #ifdef WANT_DEBUGGER
371
372 static uint8 GAS_SectorCache[2048];
373 static int32 GAS_SectorCacheWhich = -1; // disc num is |'d in after << 24
374
PCFXDBG_GetAddressSpaceBytes(const char * name,uint32 Address,uint32 Length,uint8 * Buffer)375 static void PCFXDBG_GetAddressSpaceBytes(const char *name, uint32 Address, uint32 Length, uint8 *Buffer)
376 {
377 int32 ws = 0;
378
379 if(!strcmp(name, "cpu"))
380 {
381 while(Length--)
382 {
383 Address &= 0xFFFFFFFF;
384 *Buffer = mem_rbyte(ws, Address);
385 Address++;
386 Buffer++;
387 }
388 }
389 else if(!strcmp(name, "ram"))
390 {
391 while(Length--)
392 {
393 Address &= 2048 * 1024 - 1;
394 *Buffer = RAM[Address];
395 Address++;
396 Buffer++;
397 }
398 }
399 else if(!strcmp(name, "backup"))
400 {
401 while(Length--)
402 {
403 Address &= 0x7FFF;
404 *Buffer = BackupRAM[Address];
405 Address++;
406 Buffer++;
407 }
408 }
409 else if(!strcmp(name, "exbackup"))
410 {
411 while(Length--)
412 {
413 Address &= 0x7FFF;
414 *Buffer = ExBackupRAM[Address];
415 Address++;
416 Buffer++;
417 }
418 }
419 else if(!strcmp(name, "bios"))
420 {
421 while(Length--)
422 {
423 Address &= 1024 * 1024 - 1;
424 *Buffer = BIOSROM[Address];
425 Address++;
426 Buffer++;
427 }
428 }
429 else if(!strncmp(name, "track", strlen("track")))
430 {
431 int disc = 0, track = 0, sector_base = 0;
432
433 trio_sscanf(name, "track%d-%d-%d", &disc, &track, §or_base);
434
435 while(Length--)
436 {
437 int32 sector = (Address / 2048) + sector_base;
438 int32 sector_offset = Address % 2048;
439
440 if((sector | (disc << 24)) != GAS_SectorCacheWhich)
441 {
442 if(!(*cdifs)[disc]->ReadSectors(GAS_SectorCache, sector, 1))
443 memset(GAS_SectorCache, 0, 2048);
444
445 GAS_SectorCacheWhich = sector | (disc << 24);
446 }
447
448 *Buffer = GAS_SectorCache[sector_offset];
449
450 Address++;
451 Buffer++;
452 }
453 }
454 }
455
PCFXDBG_PutAddressSpaceBytes(const char * name,uint32 Address,uint32 Length,uint32 Granularity,bool hl,const uint8 * Buffer)456 static void PCFXDBG_PutAddressSpaceBytes(const char *name, uint32 Address, uint32 Length, uint32 Granularity, bool hl, const uint8 *Buffer)
457 {
458 if(!strcmp(name, "cpu"))
459 {
460 while(Length--)
461 {
462 Address &= 0xFFFFFFFF;
463 if(Address >= 0xFFF00000 && Address <= 0xFFFFFFFF)
464 {
465 BIOSROM[Address & 0xFFFFF] = *Buffer;
466 }
467 else if(Address <= 0x1FFFFF)
468 {
469 RAM[Address & 0x1FFFFF] = *Buffer;
470 }
471 else if(Address >= 0xE0000000 && Address <= 0xE7FFFFFF)
472 {
473 if(!(Address & 1))
474 {
475 BackupSignalDirty |= (BackupRAM[(Address & 0xFFFF) >> 1] != *Buffer);
476 BackupRAM[(Address & 0xFFFF) >> 1] = *Buffer;
477 }
478 }
479 else if(Address >= 0xE8000000 && Address <= 0xE9FFFFFF)
480 {
481 if(!(Address & 1))
482 {
483 BackupSignalDirty |= (ExBackupRAM[(Address & 0xFFFF) >> 1] != *Buffer);
484 ExBackupRAM[(Address & 0xFFFF) >> 1] = *Buffer;
485 }
486 }
487 Address++;
488 Buffer++;
489 }
490 }
491 else if(!strcmp(name, "ram"))
492 {
493 while(Length--)
494 {
495 Address &= 2048 * 1024 - 1;
496 RAM[Address] = *Buffer;
497 Address++;
498 Buffer++;
499 }
500 }
501 else if(!strcmp(name, "backup"))
502 {
503 while(Length--)
504 {
505 Address &= 0x7FFF;
506 BackupSignalDirty |= (BackupRAM[Address] != *Buffer);
507 BackupRAM[Address] = *Buffer;
508 Address++;
509 Buffer++;
510 }
511 }
512 else if(!strcmp(name, "exbackup"))
513 {
514 while(Length--)
515 {
516 Address &= 0x7FFF;
517 BackupSignalDirty |= (ExBackupRAM[Address] != *Buffer);
518 ExBackupRAM[Address] = *Buffer;
519 Address++;
520 Buffer++;
521 }
522 }
523
524 else if(!strcmp(name, "bios"))
525 {
526 while(Length--)
527 {
528 Address &= 1024 * 1024 - 1;
529 BIOSROM[Address] = *Buffer;
530 Address++;
531 Buffer++;
532 }
533 }
534
535 }
536 #endif
537
VDCA_IRQHook(bool asserted)538 static void VDCA_IRQHook(bool asserted)
539 {
540 PCFXIRQ_Assert(PCFXIRQ_SOURCE_VDCA, asserted);
541 }
542
VDCB_IRQHook(bool asserted)543 static void VDCB_IRQHook(bool asserted)
544 {
545 PCFXIRQ_Assert(PCFXIRQ_SOURCE_VDCB, asserted);
546 }
547
Cleanup(void)548 static MDFN_COLD void Cleanup(void)
549 {
550 for(int i = 0; i < 2; i++)
551 {
552 if(fx_vdc_chips[i])
553 {
554 delete fx_vdc_chips[i];
555 fx_vdc_chips[i] = NULL;
556 }
557 }
558
559 RAINBOW_Close();
560 KING_Close();
561 SoundBox_Kill();
562 PCFX_V810.Kill();
563
564 // The allocated memory RAM and BIOSROM is free'd in V810_Kill()
565 RAM = NULL;
566 BIOSROM = NULL;
567 }
568
LoadCommon(std::vector<CDInterface * > * CDInterfaces)569 static MDFN_COLD void LoadCommon(std::vector<CDInterface*> *CDInterfaces)
570 {
571 V810_Emu_Mode cpu_mode;
572
573 #ifdef WANT_DEBUGGER
574 PCFXDBG_Init();
575 #endif
576
577 cpu_mode = (V810_Emu_Mode)MDFN_GetSettingI("pcfx.cpu_emulation");
578 if(cpu_mode == _V810_EMU_MODE_COUNT)
579 {
580 cpu_mode = (EmuFlags & CDGE_FLAG_ACCURATE_V810) ? V810_EMU_MODE_ACCURATE : V810_EMU_MODE_FAST;
581 }
582
583 if(EmuFlags & CDGE_FLAG_FXGA)
584 {
585 //WantHuC6273 = true;
586 }
587
588 MDFN_printf(_("V810 Emulation Mode: %s\n"), (cpu_mode == V810_EMU_MODE_ACCURATE) ? _("Accurate") : _("Fast"));
589 PCFX_V810.Init(cpu_mode, false);
590
591 uint32 RAM_Map_Addresses[1] = { 0x00000000 };
592 uint32 BIOSROM_Map_Addresses[1] = { 0xFFF00000 };
593
594 RAM = PCFX_V810.SetFastMap(RAM_Map_Addresses, 0x00200000, 1, _("RAM"));
595 BIOSROM = PCFX_V810.SetFastMap(BIOSROM_Map_Addresses, 0x00100000, 1, _("BIOS ROM"));
596
597 {
598 std::string biospath = MDFN_MakeFName(MDFNMKF_FIRMWARE, 0, MDFN_GetSettingS("pcfx.bios"));
599 static const std::vector<FileExtensionSpecStruct> KnownBIOSExtensions =
600 {
601 { ".rom", 0, "" },
602 };
603 MDFNFILE BIOSFile(&NVFS, biospath.c_str(), KnownBIOSExtensions, "BIOS");
604
605 if(BIOSFile.size() != 1024 * 1024)
606 throw MDFN_Error(0, _("BIOS ROM file is incorrect size.\n"));
607
608 BIOSFile.read(BIOSROM, 1024 * 1024);
609 BIOSFile.Close();
610 }
611
612 {
613 std::string fxscsi_path = MDFN_GetSettingS("pcfx.fxscsi"); // For developers only, so don't make it convenient.
614
615 if(fxscsi_path != "0" && fxscsi_path != "" && fxscsi_path != "none")
616 {
617 FileStream FXSCSIFile(fxscsi_path, FileStream::MODE_READ);
618 uint32 FXSCSI_Map_Addresses[1] = { 0x80780000 };
619
620 FXSCSIROM = PCFX_V810.SetFastMap(FXSCSI_Map_Addresses, 0x0080000, 1, _("FX-SCSI ROM"));
621
622 FXSCSIFile.read(FXSCSIROM, 1024 * 512);
623 }
624 }
625
626 #ifdef WANT_DEBUGGER
627 ASpace_Add(PCFXDBG_GetAddressSpaceBytes, PCFXDBG_PutAddressSpaceBytes, "cpu", "CPU Physical", 32);
628 ASpace_Add(PCFXDBG_GetAddressSpaceBytes, PCFXDBG_PutAddressSpaceBytes, "ram", "RAM", 21);
629 ASpace_Add(PCFXDBG_GetAddressSpaceBytes, PCFXDBG_PutAddressSpaceBytes, "backup", "Internal Backup Memory", 15);
630 ASpace_Add(PCFXDBG_GetAddressSpaceBytes, PCFXDBG_PutAddressSpaceBytes, "exbackup", "External Backup Memory", 15);
631 ASpace_Add(PCFXDBG_GetAddressSpaceBytes, PCFXDBG_PutAddressSpaceBytes, "bios", "BIOS ROM", 20);
632 #endif
633
634 for(int i = 0; i < 2; i++)
635 {
636 fx_vdc_chips[i] = new VDC();
637 fx_vdc_chips[i]->SetUnlimitedSprites(MDFN_GetSettingB("pcfx.nospritelimit"));
638 fx_vdc_chips[i]->SetVRAMSize(65536);
639 fx_vdc_chips[i]->SetWSHook(NULL);
640 fx_vdc_chips[i]->SetIRQHook(i ? VDCB_IRQHook : VDCA_IRQHook);
641
642 //fx_vdc_chips[0] = FXVDC_Init(PCFXIRQ_SOURCE_VDCA, MDFN_GetSettingB("pcfx.nospritelimit"));
643 //fx_vdc_chips[1] = FXVDC_Init(PCFXIRQ_SOURCE_VDCB, MDFN_GetSettingB("pcfx.nospritelimit"));
644 }
645
646 SoundBox_Init(MDFN_GetSettingB("pcfx.adpcm.emulate_buggy_codec"), MDFN_GetSettingB("pcfx.adpcm.suppress_channel_reset_clicks"));
647 RAINBOW_Init(MDFN_GetSettingB("pcfx.rainbow.chromaip"));
648 FXINPUT_Init();
649 FXTIMER_Init();
650
651 if(WantHuC6273)
652 HuC6273_Init();
653
654 KING_Init();
655
656 SCSICD_SetDisc(true, NULL, true);
657
658 #ifdef WANT_DEBUGGER
659 for(unsigned disc = 0; disc < CDInterfaces->size(); disc++)
660 {
661 CDUtility::TOC toc;
662
663 (*CDInterfaces)[disc]->ReadTOC(&toc);
664
665 for(int32 track = toc.first_track; track <= toc.last_track; track++)
666 {
667 if(toc.tracks[track].control & 0x4)
668 {
669 char tmpn[256], tmpln[256];
670 uint32 sectors;
671
672 trio_snprintf(tmpn, 256, "track%d-%d-%d", disc, track, toc.tracks[track].lba);
673 trio_snprintf(tmpln, 256, "CD - Disc %d/%d - Track %d/%d", disc + 1, (int)CDInterfaces->size(), track, toc.last_track - toc.first_track + 1);
674
675 sectors = toc.tracks[(track == toc.last_track) ? 100 : track + 1].lba - toc.tracks[track].lba;
676 ASpace_Add(PCFXDBG_GetAddressSpaceBytes, PCFXDBG_PutAddressSpaceBytes, tmpn, tmpln, 0, sectors * 2048);
677 }
678 }
679 }
680 #endif
681
682
683 MDFNGameInfo->fps = (uint32)((double)7159090.90909090 / 455 / 263 * 65536 * 256);
684
685 MDFNGameInfo->nominal_height = MDFN_GetSettingUI("pcfx.slend") - MDFN_GetSettingUI("pcfx.slstart") + 1;
686
687 // Emulation raw framebuffer image should always be of 256 width when the pcfx.high_dotclock_width setting is set to "256",
688 // but it could be either 256 or 341 when the setting is set to "341", so stay with 1024 in that case so we won't have
689 // a messed up aspect ratio in our recorded QuickTime movies.
690 MDFNGameInfo->lcm_width = (MDFN_GetSettingUI("pcfx.high_dotclock_width") == 256) ? 256 : 1024;
691 MDFNGameInfo->lcm_height = MDFNGameInfo->nominal_height;
692
693 MDFNMP_Init(1024 * 1024, ((uint64)1 << 32) / (1024 * 1024));
694 MDFNMP_AddRAM(2048 * 1024, 0x00000000, RAM);
695
696
697 BackupSignalDirty = false;
698 BackupSaveDelay = 0;
699
700 BRAMDisabled = MDFN_GetSettingB("pcfx.disable_bram");
701
702 if(BRAMDisabled)
703 MDFN_printf(_("Warning: BRAM is disabled per pcfx.disable_bram setting. This is simulating a malfunction.\n"));
704
705 if(!BRAMDisabled)
706 {
707 // Initialize backup RAM
708 memset(BackupRAM, 0, sizeof(BackupRAM));
709 memset(ExBackupRAM, 0, sizeof(ExBackupRAM));
710
711 static const uint8 BRInit00[] = { 0x24, 0x8A, 0xDF, 0x50, 0x43, 0x46, 0x58, 0x53, 0x72, 0x61, 0x6D, 0x80,
712 0x00, 0x01, 0x01, 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0xF9, 0x03, 0x00,
713 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00
714 };
715 static const uint8 BRInit80[] = { 0xF9, 0xFF, 0xFF };
716
717 memcpy(BackupRAM + 0x00, BRInit00, sizeof(BRInit00));
718 memcpy(BackupRAM + 0x80, BRInit80, sizeof(BRInit80));
719
720
721 static const uint8 ExBRInit00[] = { 0x24, 0x8A, 0xDF, 0x50, 0x43, 0x46, 0x58, 0x43, 0x61, 0x72, 0x64, 0x80,
722 0x00, 0x01, 0x01, 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0xF9, 0x03, 0x00,
723 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00
724 };
725 static const uint8 ExBRInit80[] = { 0xF9, 0xFF, 0xFF };
726
727 memcpy(ExBackupRAM + 0x00, ExBRInit00, sizeof(ExBRInit00));
728 memcpy(ExBackupRAM + 0x80, ExBRInit80, sizeof(ExBRInit80));
729
730 try
731 {
732 std::string save_path = MDFN_MakeFName(MDFNMKF_SAV, 0, "sav");
733 GZFileStream savefp(save_path, GZFileStream::MODE::READ);
734 const uint64 fp_size_tmp = savefp.size();
735
736 if(fp_size_tmp != 65536)
737 throw MDFN_Error(0, _("Save game memory file \"%s\" is an incorrect size(%llu bytes). The correct size is %llu bytes."), save_path.c_str(), (unsigned long long)fp_size_tmp, (unsigned long long)65536);
738
739 savefp.read(BackupRAM, 0x8000);
740 savefp.read(ExBackupRAM, 0x8000);
741 }
742 catch(MDFN_Error &e)
743 {
744 if(e.GetErrno() != ENOENT)
745 throw;
746 }
747 }
748
749 // Default to 16-bit bus.
750 for(int i = 0; i < 256; i++)
751 {
752 PCFX_V810.SetMemReadBus32(i, false);
753 PCFX_V810.SetMemWriteBus32(i, false);
754 }
755
756 // 16MiB RAM area.
757 PCFX_V810.SetMemReadBus32(0, true);
758 PCFX_V810.SetMemWriteBus32(0, true);
759
760 // Bitstring read range
761 for(int i = 0xA0; i <= 0xAF; i++)
762 {
763 PCFX_V810.SetMemReadBus32(i, false); // Reads to the read range are 16-bit, and
764 PCFX_V810.SetMemWriteBus32(i, true); // writes are 32-bit.
765 }
766
767 // Bitstring write range
768 for(int i = 0xB0; i <= 0xBF; i++)
769 {
770 PCFX_V810.SetMemReadBus32(i, true); // Reads to the write range are 32-bit,
771 PCFX_V810.SetMemWriteBus32(i, false); // but writes are 16-bit!
772 }
773
774 // BIOS area
775 for(int i = 0xF0; i <= 0xFF; i++)
776 {
777 PCFX_V810.SetMemReadBus32(i, false);
778 PCFX_V810.SetMemWriteBus32(i, false);
779 }
780
781 PCFX_V810.SetMemReadHandlers(mem_rbyte, mem_rhword, mem_rword);
782 PCFX_V810.SetMemWriteHandlers(mem_wbyte, mem_whword, mem_wword);
783
784 PCFX_V810.SetIOReadHandlers(port_rbyte, port_rhword, NULL);
785 PCFX_V810.SetIOWriteHandlers(port_wbyte, port_whword, NULL);
786 }
787
DoMD5CDVoodoo(std::vector<CDInterface * > * CDInterfaces)788 static void DoMD5CDVoodoo(std::vector<CDInterface*> *CDInterfaces)
789 {
790 const CDGameEntry *found_entry = NULL;
791 CDUtility::TOC toc;
792
793 #if 0
794 puts("{");
795 puts(" ,");
796 puts(" ,");
797 puts(" 0,");
798 puts(" 1,");
799 puts(" {");
800 puts(" {");
801
802 for(int i = CDIF_GetFirstTrack(); i <= CDIF_GetLastTrack(); i++)
803 {
804 CDIF_Track_Format tf;
805
806 CDIF_GetTrackFormat(i, tf);
807
808 printf(" { %d, %s, %d },\n", i, (tf == CDIF_FORMAT_AUDIO) ? "CDIF_FORMAT_AUDIO" : "CDIF_FORMAT_MODE1", CDIF_GetTrackStartPositionLBA(i));
809 }
810 printf(" { -1, (CDIF_Track_Format)-1, %d },\n", CDIF_GetSectorCountLBA());
811 puts(" }");
812 puts(" }");
813 puts("},");
814 //exit(1);
815 #endif
816
817 for(unsigned if_disc = 0; if_disc < CDInterfaces->size(); if_disc++)
818 {
819 (*CDInterfaces)[if_disc]->ReadTOC(&toc);
820
821 if(toc.first_track == 1)
822 {
823 for(unsigned int g = 0; g < sizeof(GameList) / sizeof(CDGameEntry); g++)
824 {
825 const CDGameEntry *entry = &GameList[g];
826
827 assert(entry->discs == 1 || entry->discs == 2);
828
829 for(unsigned int disc = 0; disc < entry->discs; disc++)
830 {
831 const CDGameEntryTrack *et = entry->tracks[disc];
832 bool GameFound = true;
833
834 while(et->tracknum != -1 && GameFound)
835 {
836 assert(et->tracknum > 0 && et->tracknum < 100);
837
838 if(toc.tracks[et->tracknum].lba != et->lba)
839 GameFound = false;
840
841 if( ((et->format == CDGE_FORMAT_DATA) ? 0x4 : 0x0) != (toc.tracks[et->tracknum].control & 0x4))
842 GameFound = false;
843
844 et++;
845 }
846
847 if(et->tracknum == -1)
848 {
849 if((et - 1)->tracknum != toc.last_track)
850 GameFound = false;
851
852 if(et->lba != toc.tracks[100].lba)
853 GameFound = false;
854 }
855
856 if(GameFound)
857 {
858 found_entry = entry;
859 goto FoundIt;
860 }
861 } // End disc count loop
862 }
863 }
864
865 FoundIt: ;
866
867 if(found_entry)
868 {
869 EmuFlags = found_entry->flags;
870 //printf("%s\n", found_entry->name);
871 MDFNGameInfo->name = std::string(found_entry->name);
872 break;
873 }
874 } // end: for(unsigned if_disc = 0; if_disc < CDInterfaces->size(); if_disc++)
875
876 MDFN_printf(_("CD Layout MD5: 0x%s\n"), md5_context::asciistr(MDFNGameInfo->MD5, 0).c_str());
877 }
878
879 // PC-FX BIOS will look at all data tracks(not just the first one), in contrast to the PCE CD BIOS, which only looks
880 // at the first data track.
TestMagicCD(std::vector<CDInterface * > * CDInterfaces)881 static bool TestMagicCD(std::vector<CDInterface*> *CDInterfaces)
882 {
883 CDInterface* cdiface = (*CDInterfaces)[0];
884 CDUtility::TOC toc;
885 uint8 sector_buffer[2048];
886
887 memset(sector_buffer, 0, sizeof(sector_buffer));
888
889 cdiface->ReadTOC(&toc);
890
891 for(int32 track = toc.first_track; track <= toc.last_track; track++)
892 {
893 if(toc.tracks[track].control & 0x4)
894 {
895 int m = cdiface->ReadSectors(sector_buffer, toc.tracks[track].lba, 1);
896
897 if(m == 0x1 && !strncmp("PC-FX:Hu_CD-ROM", (char*)sector_buffer, strlen("PC-FX:Hu_CD-ROM")))
898 return true;
899 else if((m == 0x1 || m == 0x2) && !strncmp((char *)sector_buffer + 64, "PPPPHHHHOOOOTTTTOOOO____CCCCDDDD", 32))
900 return true;
901 }
902 }
903 return false;
904 }
905
LoadCD(std::vector<CDInterface * > * CDInterfaces)906 static MDFN_COLD void LoadCD(std::vector<CDInterface*> *CDInterfaces)
907 {
908 try
909 {
910 EmuFlags = 0;
911
912 cdifs = CDInterfaces;
913
914 DoMD5CDVoodoo(CDInterfaces);
915
916 LoadCommon(CDInterfaces);
917
918 MDFN_printf(_("Emulated CD-ROM drive speed: %ux\n"), (unsigned int)MDFN_GetSettingUI("pcfx.cdspeed"));
919
920 PCFX_Power();
921 }
922 catch(...)
923 {
924 Cleanup();
925 throw;
926 }
927 }
928
SetMedia(uint32 drive_idx,uint32 state_idx,uint32 media_idx,uint32 orientation_idx)929 static void SetMedia(uint32 drive_idx, uint32 state_idx, uint32 media_idx, uint32 orientation_idx)
930 {
931 const RMD_Layout* rmd = EmulatedPCFX.RMD;
932 const RMD_Drive* rd = &rmd->Drives[drive_idx];
933 const RMD_State* rs = &rd->PossibleStates[state_idx];
934
935 if(rs->MediaPresent && rs->MediaUsable)
936 {
937 SCSICD_SetDisc(false, (*cdifs)[media_idx]);
938 }
939 else
940 {
941 SCSICD_SetDisc(rs->MediaCanChange, NULL);
942 }
943 }
944
SaveBackupMemory(void)945 static void SaveBackupMemory(void)
946 {
947 if(!BRAMDisabled)
948 {
949 FileStream fp(MDFN_MakeFName(MDFNMKF_SAV, 0, "sav"), FileStream::MODE_WRITE_INPLACE);
950
951 fp.write(BackupRAM, 0x8000);
952 fp.write(ExBackupRAM, 0x8000);
953
954 fp.truncate(fp.tell());
955 fp.close();
956 }
957 }
958
CloseGame(void)959 static MDFN_COLD void CloseGame(void)
960 {
961 try
962 {
963 SaveBackupMemory();
964 }
965 catch(std::exception &e)
966 {
967 MDFN_Notify(MDFN_NOTICE_ERROR, _("Error saving save-game memory: %s"), e.what());
968 }
969
970 Cleanup();
971 }
972
DoSimpleCommand(int cmd)973 static void DoSimpleCommand(int cmd)
974 {
975 switch(cmd)
976 {
977 case MDFN_MSC_RESET: PCFX_Reset(); break;
978 case MDFN_MSC_POWER: PCFX_Power(); break;
979 }
980 }
981
StateAction(StateMem * sm,const unsigned load,const bool data_only)982 static void StateAction(StateMem *sm, const unsigned load, const bool data_only)
983 {
984 const v810_timestamp_t timestamp = PCFX_V810.v810_timestamp;
985
986 SFORMAT StateRegs[] =
987 {
988 SFPTR8(RAM, 0x200000),
989 SFVAR(Last_VDC_AR),
990 SFVAR(RAM_LPA),
991 SFVAR(BackupControl),
992 SFVAR(ExBusReset),
993 SFPTR8(BackupRAM, BRAMDisabled ? 0 : 0x8000),
994 SFPTR8(ExBackupRAM, BRAMDisabled ? 0 : 0x8000),
995
996 SFEND
997 };
998
999 MDFNSS_StateAction(sm, load, data_only, StateRegs, "MAIN");
1000
1001 for(int i = 0; i < 2; i++)
1002 fx_vdc_chips[i]->StateAction(sm, load, data_only, i ? "VDC1" : "VDC0");
1003
1004 FXINPUT_StateAction(sm, load, data_only);
1005 PCFXIRQ_StateAction(sm, load, data_only);
1006 KING_StateAction(sm, load, data_only);
1007 PCFX_V810.StateAction(sm, load, data_only);
1008 FXTIMER_StateAction(sm, load, data_only);
1009 SoundBox_StateAction(sm, load, data_only);
1010 SCSICD_StateAction(sm, load, data_only, "CDRM");
1011 RAINBOW_StateAction(sm, load, data_only);
1012
1013 if(load)
1014 {
1015 //
1016 // Rather than bothering to store next event timestamp deltas in save states, we'll just recalculate next event times on save state load as a side effect
1017 // of this call.
1018 //
1019 ForceEventUpdates(timestamp);
1020
1021 if(!BRAMDisabled)
1022 BackupSignalDirty = true;
1023 }
1024
1025 //printf("0x%08x, %d %d %d %d\n", load, next_pad_ts, next_timer_ts, next_adpcm_ts, next_king_ts);
1026 }
1027
1028 static const MDFNSetting_EnumList V810Mode_List[] =
1029 {
1030 { "fast", (int)V810_EMU_MODE_FAST, gettext_noop("Fast Mode"), gettext_noop("Fast mode trades timing accuracy, cache emulation, and executing from hardware registers and RAM not intended for code use for performance.")},
1031 { "accurate", (int)V810_EMU_MODE_ACCURATE, gettext_noop("Accurate Mode"), gettext_noop("Increased timing accuracy, though not perfect, along with cache emulation, at the cost of decreased performance. Additionally, even the pipeline isn't correctly and fully emulated in this mode.") },
1032 { "auto", (int)_V810_EMU_MODE_COUNT, gettext_noop("Auto Mode"), gettext_noop("Selects \"fast\" or \"accurate\" automatically based on an internal database. If the CD image is not recognized, defaults to \"fast\".") },
1033 { NULL, 0 },
1034 };
1035
1036
1037 static const MDFNSetting_EnumList HDCWidthList[] =
1038 {
1039 { "256", 256, "256 pixels", gettext_noop("This value will cause heavy pixel distortion.") },
1040 { "341", 341, "341 pixels", gettext_noop("This value will cause moderate pixel distortion.") },
1041 { "1024", 1024, "1024 pixels", gettext_noop("This value will cause no pixel distortion as long as interpolation is enabled on the video output device and the resolution is sufficiently high, but it will use a lot of CPU time.") },
1042 { NULL, 0 },
1043 };
1044
1045 static const MDFNSetting PCFXSettings[] =
1046 {
1047 { "pcfx.input.port1.multitap", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("Enable multitap on PC-FX port 1."), gettext_noop("EXPERIMENTAL emulation of the unreleased multitap. Enables ports 3 4 5."), MDFNST_BOOL, "0", NULL, NULL },
1048 { "pcfx.input.port2.multitap", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("Enable multitap on PC-FX port 2."), gettext_noop("EXPERIMENTAL emulation of the unreleased multitap. Enables ports 6 7 8."), MDFNST_BOOL, "0", NULL, NULL },
1049
1050
1051 { "pcfx.mouse_sensitivity", MDFNSF_NOFLAGS, gettext_noop("Mouse sensitivity."), NULL, MDFNST_FLOAT, "1.25", NULL, NULL },
1052 { "pcfx.disable_softreset", MDFNSF_NOFLAGS, gettext_noop("When RUN+SEL are pressed simultaneously, disable both buttons temporarily."), NULL, MDFNST_BOOL, "0", NULL, NULL, NULL, FXINPUT_SettingChanged },
1053
1054 { "pcfx.cpu_emulation", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("CPU emulation mode."), NULL, MDFNST_ENUM, "auto", NULL, NULL, NULL, NULL, V810Mode_List },
1055 { "pcfx.bios", MDFNSF_EMU_STATE | MDFNSF_CAT_PATH, gettext_noop("Path to the ROM BIOS"), NULL, MDFNST_STRING, "pcfx.rom" },
1056 { "pcfx.fxscsi", MDFNSF_EMU_STATE | MDFNSF_CAT_PATH, gettext_noop("Path to the FX-SCSI ROM"), gettext_noop("Intended for developers only."), MDFNST_STRING, "0" },
1057 { "pcfx.disable_bram", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("Disable internal and external BRAM."), gettext_noop("It is intended for viewing games' error screens that may be different from simple BRAM full and uninitialized BRAM error screens, though it can cause the game to crash outright."), MDFNST_BOOL, "0" },
1058 { "pcfx.cdspeed", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("Emulated CD-ROM speed."), gettext_noop("Setting the value higher than 2, the default, will decrease loading times in most games by some degree."), MDFNST_UINT, "2", "2", "10" },
1059
1060 { "pcfx.nospritelimit", MDFNSF_NOFLAGS, gettext_noop("Remove 16-sprites-per-scanline hardware limit."), NULL, MDFNST_BOOL, "0" },
1061 { "pcfx.high_dotclock_width", MDFNSF_NOFLAGS, gettext_noop("Emulated width for 7.16MHz dot-clock mode."), gettext_noop("Lower values are faster, but will cause some degree of pixel distortion."), MDFNST_ENUM, "1024", NULL, NULL, NULL, NULL, HDCWidthList },
1062
1063 { "pcfx.slstart", MDFNSF_NOFLAGS, gettext_noop("First rendered scanline."), NULL, MDFNST_UINT, "4", "0", "239" },
1064 { "pcfx.slend", MDFNSF_NOFLAGS, gettext_noop("Last rendered scanline."), NULL, MDFNST_UINT, "235", "0", "239" },
1065
1066 { "pcfx.rainbow.chromaip", MDFNSF_NOFLAGS, gettext_noop("Enable bilinear interpolation on the chroma channel of RAINBOW YUV output."), gettext_noop("This is an enhancement-related setting. Enabling it may cause graphical glitches with some games."), MDFNST_BOOL, "0" },
1067
1068 { "pcfx.adpcm.suppress_channel_reset_clicks", MDFNSF_NOFLAGS, gettext_noop("Hack to suppress clicks caused by forced channel resets."), NULL, MDFNST_BOOL, "1" },
1069
1070 // Hack that emulates the codec a buggy ADPCM encoder used for some games' ADPCM. Not enabled by default because it makes some games(with
1071 //correctly-encoded ADPCM?) sound worse
1072 { "pcfx.adpcm.emulate_buggy_codec", MDFNSF_NOFLAGS, gettext_noop("Hack that emulates the codec a buggy ADPCM encoder used for some games' ADPCM."), NULL, MDFNST_BOOL, "0" },
1073
1074 { "pcfx.resamp_quality", MDFNSF_NOFLAGS, gettext_noop("Sound quality."), gettext_noop("Higher values correspond to better SNR and better preservation of higher frequencies(\"brightness\"), at the cost of increased computational complexity and a negligible increase in latency."), MDFNST_INT, "3", "0", "5" },
1075 { "pcfx.resamp_rate_error", MDFNSF_NOFLAGS, gettext_noop("Output rate tolerance."), gettext_noop("Lower values correspond to better matching of the output rate of the resampler to the actual desired output rate, at the expense of increased RAM usage and poorer CPU cache utilization."), MDFNST_FLOAT, "0.0000009", "0.0000001", "0.0000350" },
1076
1077 { NULL }
1078 };
1079
1080
1081 static const FileExtensionSpecStruct KnownExtensions[] =
1082 {
1083 //{ ".ex", gettext_noop("PC-FX HuEXE") },
1084 { NULL, 0, NULL }
1085 };
1086
1087 }
1088
1089 using namespace MDFN_IEN_PCFX;
1090
1091 MDFNGI EmulatedPCFX =
1092 {
1093 "pcfx",
1094 "PC-FX",
1095 KnownExtensions,
1096 MODPRIO_INTERNAL_HIGH,
1097 #ifdef WANT_DEBUGGER
1098 &PCFXDBGInfo,
1099 #else
1100 NULL,
1101 #endif
1102 PCFXPortInfo,
1103 NULL,
1104 NULL,
1105 NULL,
1106 LoadCD,
1107 TestMagicCD,
1108 CloseGame,
1109
1110 KING_SetLayerEnableMask,
1111 "BG0\0BG1\0BG2\0BG3\0VDC-A BG\0VDC-A SPR\0VDC-B BG\0VDC-B SPR\0RAINBOW\0",
1112
1113 NULL,
1114 NULL,
1115
1116 NULL,
1117 0,
1118
1119 CheatInfo_Empty,
1120
1121 false,
1122 StateAction,
1123 Emulate,
1124 FXINPUT_TransformInput,
1125 FXINPUT_SetInput,
1126 SetMedia,
1127 DoSimpleCommand,
1128 NULL,
1129 PCFXSettings,
1130 MDFN_MASTERCLOCK_FIXED(PCFX_MASTER_CLOCK),
1131 0,
1132 true, // Multires possible?
1133
1134 0, // lcm_width
1135 0, // lcm_height
1136 NULL, // Dummy
1137
1138 288, // Nominal width
1139 240, // Nominal height
1140
1141 1024, // Framebuffer width
1142 512, // Framebuffer height
1143
1144 2, // Number of output sound channels
1145 };
1146
1147