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, &sector_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