1 /******************************************************************************/
2 /* Mednafen Sega Saturn Emulation Module */
3 /******************************************************************************/
4 /* ss.cpp - Saturn Core Emulation and Support Functions
5 ** Copyright (C) 2015-2020 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 // WARNING: Be careful with 32-bit access to 16-bit space, bus locking, etc. in respect to DMA and event updates(and where they can occur).
23
24 #include <mednafen/mednafen.h>
25 #include <mednafen/cdrom/CDInterface.h>
26 #include <mednafen/general.h>
27 #include <mednafen/FileStream.h>
28 #include <mednafen/compress/GZFileStream.h>
29 #include <mednafen/mempatcher.h>
30 #include <mednafen/hash/sha256.h>
31 #include <mednafen/hash/md5.h>
32 #include <mednafen/Time.h>
33
34 #include <bitset>
35
36 #include <trio/trio.h>
37
38 #include <zlib.h>
39
40 #if defined(HAVE_SSE2_INTRINSICS)
41 #include <xmmintrin.h>
42 #include <emmintrin.h>
43 #endif
44
45 using namespace Mednafen;
46
47 MDFN_HIDE extern MDFNGI EmulatedSS;
48
49 #include "ss.h"
50 #include "sound.h"
51 #include "scsp.h" // For debug.inc
52 #include "smpc.h"
53 #include "cdb.h"
54 #include "vdp1.h"
55 #include "vdp2.h"
56 #include "scu.h"
57 #include "cart.h"
58 #include "db.h"
59
60 namespace MDFN_IEN_SS
61 {
62
63 static sscpu_timestamp_t MidSync(const sscpu_timestamp_t timestamp);
64
65 #ifdef MDFN_ENABLE_DEV_BUILD
66 uint32 ss_dbg_mask;
67 static std::bitset<0x200> BWMIgnoreAddr[2]; // 0=read, 1=write
68
SS_DBG(uint32 which,const char * format,...)69 void SS_DBG(uint32 which, const char* format, ...)
70 {
71 if(MDFN_LIKELY(!(ss_dbg_mask & which)))
72 return;
73 //
74 va_list ap;
75 va_start(ap, format);
76 trio_vprintf(format, ap);
77 va_end(ap);
78 }
79
SS_DBGTI(uint32 which,const char * format,...)80 void SS_DBGTI(uint32 which, const char* format, ...)
81 {
82 if(MDFN_LIKELY(!(ss_dbg_mask & which)))
83 return;
84 //
85 va_list ap;
86 va_start(ap, format);
87 trio_vprintf(format, ap);
88 va_end(ap);
89 //
90 trio_printf(" @Line=0x%03x, HPos=0x%03x, memts=%d\n", VDP2::PeekLine(), VDP2::PeekHPos(), SH7095_mem_timestamp);
91 }
92 #endif
93 uint32 ss_horrible_hacks;
94
95 static bool NeedEmuICache;
96 static const uint8 BRAM_Init_Data[0x10] = { 0x42, 0x61, 0x63, 0x6b, 0x55, 0x70, 0x52, 0x61, 0x6d, 0x20, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74 };
97
98 static void SaveBackupRAM(void);
99 static void LoadBackupRAM(void);
100 static void SaveCartNV(void);
101 static void LoadCartNV(void);
102 static void SaveRTC(void);
103 static void LoadRTC(void);
104
105 static MDFN_COLD void BackupBackupRAM(void);
106 static MDFN_COLD void BackupCartNV(void);
107
108 #include "sh7095.h"
109
110 static uint8 SCU_MSH2VectorFetch(void);
111 static uint8 SCU_SSH2VectorFetch(void);
112
113 static void INLINE MDFN_HOT CheckEventsByMemTS(void);
114
115 SH7095 CPU[2]{ {"SH2-M", SS_EVENT_SH2_M_DMA, SCU_MSH2VectorFetch}, {"SH2-S", SS_EVENT_SH2_S_DMA, SCU_SSH2VectorFetch}};
116 static uint16 BIOSROM[524288 / sizeof(uint16)];
117 static uint16 WorkRAML[1024 * 1024 / sizeof(uint16)];
118 static uint16 WorkRAMH[1024 * 1024 / sizeof(uint16)]; // Effectively 32-bit in reality, but 16-bit here because of CPU interpreter design(regarding fastmap).
119 static uint8 BackupRAM[32768];
120 static bool BackupRAM_Dirty;
121 static int64 BackupRAM_SaveDelay;
122 static int64 CartNV_SaveDelay;
123
124 #define SH7095_EXT_MAP_GRAN_BITS 16
125 static uintptr_t SH7095_FastMap[1U << (32 - SH7095_EXT_MAP_GRAN_BITS)];
126
127 int32 SH7095_mem_timestamp;
128 static uint32 SH7095_BusLock;
129 static uint32 SH7095_DB;
130 #include "scu.inc"
131
132 #include "debug.inc"
133
134 static sha256_digest BIOS_SHA256; // SHA-256 hash of the currently-loaded BIOS; used for save state sanity checks.
135 static int ActiveCartType; // Used in save states.
136 static std::vector<CDInterface*> *cdifs = NULL;
137 static std::bitset<1U << (27 - SH7095_EXT_MAP_GRAN_BITS)> FMIsWriteable;
138 static uint16 fmap_dummy[(1U << SH7095_EXT_MAP_GRAN_BITS) / sizeof(uint16)];
139
140 /*
141 SH-2 external bus address map:
142 CS0: 0x00000000...0x01FFFFFF (16-bit)
143 0x00000000...0x000FFFFF: BIOS ROM (R)
144 0x00100000...0x0017FFFF: SMPC (R/W; 8-bit mapped as 16-bit)
145 0x00180000...0x001FFFFF: Backup RAM(32KiB) (R/W; 8-bit mapped as 16-bit)
146 0x00200000...0x003FFFFF: Low RAM(1MiB) (R/W)
147 0x01000000...0x017FFFFF: Slave FRT Input Capture Trigger (W)
148 0x01800000...0x01FFFFFF: Master FRT Input Capture Trigger (W)
149
150 CS1: 0x02000000...0x03FFFFFF (SCU managed)
151 0x02000000...0x03FFFFFF: A-bus CS0 (R/W)
152
153 CS2: 0x04000000...0x05FFFFFF (SCU managed)
154 0x04000000...0x04FFFFFF: A-bus CS1 (R/W)
155 0x05000000...0x057FFFFF: A-bus Dummy
156 0x05800000...0x058FFFFF: A-bus CS2 (R/W)
157 0x05A00000...0x05AFFFFF: SCSP RAM (R/W)
158 0x05B00000...0x05BFFFFF: SCSP Registers (R/W)
159 0x05C00000...0x05C7FFFF: VDP1 VRAM (R/W)
160 0x05C80000...0x05CFFFFF: VDP1 FB RAM (R/W; swappable between two framebuffers, but may be temporarily unreadable at swap time)
161 0x05D00000...0x05D7FFFF: VDP1 Registers (R/W)
162 0x05E00000...0x05EFFFFF: VDP2 VRAM (R/W)
163 0x05F00000...0x05F7FFFF: VDP2 CRAM (R/W; 8-bit writes are illegal)
164 0x05F80000...0x05FBFFFF: VDP2 Registers (R/W; 8-bit writes are illegal)
165 0x05FE0000...0x05FEFFFF: SCU Registers (R/W)
166 0x05FF0000...0x05FFFFFF: SCU Debug/Test Registers (R/W)
167
168 CS3: 0x06000000...0x07FFFFFF
169 0x06000000...0x07FFFFFF: High RAM/SDRAM(1MiB) (R/W)
170 */
171 //
172 // Never add anything to SH7095_mem_timestamp when DMAHax is true.
173 //
174 // When BurstHax is true and we're accessing high work RAM, don't add anything.
175 //
176 template<typename T, bool IsWrite>
BusRW_DB_CS0(const uint32 A,uint32 & DB,const bool BurstHax,int32 * SH2DMAHax)177 static INLINE void BusRW_DB_CS0(const uint32 A, uint32& DB, const bool BurstHax, int32* SH2DMAHax)
178 {
179 //
180 // Low(and kinda slow) work RAM
181 //
182 if(A >= 0x00200000 && A <= 0x003FFFFF)
183 {
184 if(A & 0x100000)
185 {
186 if(IsWrite)
187 SS_DBG(SS_DBG_WARNING, "[RAM] %zu-byte write of 0x%08x to mirrored address 0x%08x\n", sizeof(T), DB >> (((A & 1) ^ (2 - sizeof(T))) << 3), A);
188 else
189 SS_DBG(SS_DBG_WARNING, "[RAM] %zu-byte read from mirrored address 0x%08x\n", sizeof(T), A);
190 }
191
192 if(IsWrite)
193 ne16_wbo_be<T>(WorkRAML, A & 0xFFFFF, DB >> (((A & 1) ^ (2 - sizeof(T))) << 3));
194 else
195 DB = (DB & 0xFFFF0000) | ne16_rbo_be<uint16>(WorkRAML, A & 0xFFFFE);
196
197 if(!SH2DMAHax)
198 SH7095_mem_timestamp += 7;
199 else
200 *SH2DMAHax += 7;
201
202 return;
203 }
204
205 //
206 // BIOS ROM
207 //
208 if(A >= 0x00000000 && A <= 0x000FFFFF)
209 {
210 if(!SH2DMAHax)
211 SH7095_mem_timestamp += 8;
212 else
213 *SH2DMAHax += 8;
214
215 if(!IsWrite)
216 DB = (DB & 0xFFFF0000) | ne16_rbo_be<uint16>(BIOSROM, A & 0x7FFFE);
217
218 return;
219 }
220
221 //
222 // SMPC
223 //
224 if(A >= 0x00100000 && A <= 0x0017FFFF)
225 {
226 const uint32 SMPC_A = (A & 0x7F) >> 1;
227
228 if(!SH2DMAHax)
229 {
230 // SH7095_mem_timestamp += 2;
231 CheckEventsByMemTS();
232 }
233
234 if(IsWrite)
235 {
236 if(sizeof(T) == 2 || (A & 1))
237 SMPC_Write(SH7095_mem_timestamp, SMPC_A, DB);
238 }
239 else
240 DB = (DB & 0xFFFF0000) | 0xFF00 | SMPC_Read(SH7095_mem_timestamp, SMPC_A);
241
242 return;
243 }
244
245 //
246 // Backup RAM
247 //
248 if(A >= 0x00180000 && A <= 0x001FFFFF)
249 {
250 if(!SH2DMAHax)
251 SH7095_mem_timestamp += 8;
252 else
253 *SH2DMAHax += 8;
254
255 if(IsWrite)
256 {
257 if(sizeof(T) != 1 || (A & 1))
258 {
259 BackupRAM[(A >> 1) & 0x7FFF] = DB;
260 BackupRAM_Dirty = true;
261 }
262 }
263 else
264 DB = (DB & 0xFFFF0000) | 0xFF00 | BackupRAM[(A >> 1) & 0x7FFF];
265
266 return;
267 }
268
269 //
270 // FRT trigger region
271 //
272 if(A >= 0x01000000 && A <= 0x01FFFFFF)
273 {
274 if(!SH2DMAHax)
275 SH7095_mem_timestamp += 8;
276 else
277 *SH2DMAHax += 8;
278
279 if(IsWrite)
280 {
281 if(sizeof(T) != 1)
282 {
283 const unsigned c = ((A >> 23) & 1) ^ 1;
284
285 CPU[c].SetFTI(true);
286 CPU[c].SetFTI(false);
287 }
288 }
289 return;
290 }
291
292 //
293 //
294 //
295 if(!SH2DMAHax)
296 SH7095_mem_timestamp += 4;
297 else
298 *SH2DMAHax += 4;
299
300 if(IsWrite)
301 SS_DBG(SS_DBG_WARNING, "[SH2 BUS] Unknown %zu-byte write of 0x%08x to 0x%08x\n", sizeof(T), DB >> (((A & 1) ^ (2 - sizeof(T))) << 3), A);
302 else
303 SS_DBG(SS_DBG_WARNING, "[SH2 BUS] Unknown %zu-byte read from 0x%08x\n", sizeof(T), A);
304 }
305
306 template<typename T, bool IsWrite>
BusRW_DB_CS12(const uint32 A,uint32 & DB,const bool BurstHax,int32 * SH2DMAHax)307 static INLINE void BusRW_DB_CS12(const uint32 A, uint32& DB, const bool BurstHax, int32* SH2DMAHax)
308 {
309 //
310 // CS1 and CS2: SCU
311 //
312 if(!IsWrite)
313 DB = 0;
314
315 SCU_FromSH2_BusRW_DB<T, IsWrite>(A, &DB, SH2DMAHax);
316 }
317
318 template<typename T, bool IsWrite>
BusRW_DB_CS3(const uint32 A,uint32 & DB,const bool BurstHax,int32 * SH2DMAHax)319 static INLINE void BusRW_DB_CS3(const uint32 A, uint32& DB, const bool BurstHax, int32* SH2DMAHax)
320 {
321 //
322 // CS3: High work RAM/SDRAM, 0x06000000 ... 0x07FFFFFF
323 //
324 // Timing is handled in BSC_BusWrite() and BSC_BusRead() in sh7095.inc
325 //
326 if(!IsWrite || sizeof(T) == 4)
327 ne16_rwbo_be<uint32, IsWrite>(WorkRAMH, A & 0xFFFFC, &DB);
328 else
329 ne16_wbo_be<T>(WorkRAMH, A & 0xFFFFF, DB >> (((A & 3) ^ (4 - sizeof(T))) << 3));
330 }
331
332 //
333 //
334 //
CheatMemRead(uint32 A)335 static MDFN_COLD uint8 CheatMemRead(uint32 A)
336 {
337 A &= (1U << 27) - 1;
338
339 return ne16_rbo_be<uint8>(SH7095_FastMap[A >> SH7095_EXT_MAP_GRAN_BITS], A);
340 }
341
CheatMemWrite(uint32 A,uint8 V)342 static MDFN_COLD void CheatMemWrite(uint32 A, uint8 V)
343 {
344 A &= (1U << 27) - 1;
345
346 if(FMIsWriteable[A >> SH7095_EXT_MAP_GRAN_BITS])
347 {
348 ne16_wbo_be<uint8>(SH7095_FastMap[A >> SH7095_EXT_MAP_GRAN_BITS], A, V);
349
350 for(unsigned c = 0; c < 2; c++)
351 {
352 if(CPU[c].CCR & SH7095::CCR_CE)
353 {
354 for(uint32 Abase = 0x00000000; Abase < 0x20000000; Abase += 0x08000000)
355 {
356 CPU[c].Cache_WriteUpdate<uint8>(Abase + A, V);
357 }
358 }
359 }
360 }
361 }
362 //
363 //
364 //
SetFastMemMap(uint32 Astart,uint32 Aend,uint16 * ptr,uint32 length,bool is_writeable)365 static void SetFastMemMap(uint32 Astart, uint32 Aend, uint16* ptr, uint32 length, bool is_writeable)
366 {
367 const uint64 Abound = (uint64)Aend + 1;
368 assert((Astart & ((1U << SH7095_EXT_MAP_GRAN_BITS) - 1)) == 0);
369 assert((Abound & ((1U << SH7095_EXT_MAP_GRAN_BITS) - 1)) == 0);
370 assert((length & ((1U << SH7095_EXT_MAP_GRAN_BITS) - 1)) == 0);
371 assert(length > 0);
372 assert(length <= (Abound - Astart));
373
374 for(uint64 A = Astart; A < Abound; A += (1U << SH7095_EXT_MAP_GRAN_BITS))
375 {
376 uintptr_t tmp = (uintptr_t)ptr + ((A - Astart) % length);
377
378 if(A < (1U << 27))
379 FMIsWriteable[A >> SH7095_EXT_MAP_GRAN_BITS] = is_writeable;
380
381 SH7095_FastMap[A >> SH7095_EXT_MAP_GRAN_BITS] = tmp - A;
382 }
383 }
384
InitFastMemMap(void)385 static MDFN_COLD void InitFastMemMap(void)
386 {
387 for(unsigned i = 0; i < sizeof(fmap_dummy) / sizeof(fmap_dummy[0]); i++)
388 {
389 fmap_dummy[i] = 0;
390 }
391
392 FMIsWriteable.reset();
393 MDFNMP_Init(1ULL << SH7095_EXT_MAP_GRAN_BITS, (1ULL << 27) / (1ULL << SH7095_EXT_MAP_GRAN_BITS));
394
395 for(uint64 A = 0; A < 1ULL << 32; A += (1U << SH7095_EXT_MAP_GRAN_BITS))
396 {
397 SH7095_FastMap[A >> SH7095_EXT_MAP_GRAN_BITS] = (uintptr_t)fmap_dummy - A;
398 }
399 }
400
SS_SetPhysMemMap(uint32 Astart,uint32 Aend,uint16 * ptr,uint32 length,bool is_writeable)401 void SS_SetPhysMemMap(uint32 Astart, uint32 Aend, uint16* ptr, uint32 length, bool is_writeable)
402 {
403 assert(Astart < 0x20000000);
404 assert(Aend < 0x20000000);
405
406 if(!ptr)
407 {
408 ptr = fmap_dummy;
409 length = sizeof(fmap_dummy);
410 }
411
412 for(uint32 Abase = 0; Abase < 0x40000000; Abase += 0x20000000)
413 SetFastMemMap(Astart + Abase, Aend + Abase, ptr, length, is_writeable);
414 }
415
416 #include "sh7095.inc"
417
418 //
419 // Running is:
420 // 0 at end of (emulation) frame
421 //
422 // 1 during normal execution
423 //
424 // -1 when we need to temporarily break out of the execution loop to
425 // e.g. handle turning the slave CPU on or off, which we can't
426 // safely do from an event handler due to the event handler
427 // potentially being called from deep within the memory read/write
428 // functions.
429 //
430 static int Running;
431 event_list_entry events[SS_EVENT__COUNT];
432 static sscpu_timestamp_t next_event_ts;
433
434 template<unsigned c>
SH_DMA_EventHandler(sscpu_timestamp_t et)435 static sscpu_timestamp_t SH_DMA_EventHandler(sscpu_timestamp_t et)
436 {
437 if(et < SH7095_mem_timestamp)
438 {
439 //printf("SH-2 DMA %d reschedule %d->%d\n", c, et, SH7095_mem_timestamp);
440 return SH7095_mem_timestamp;
441 }
442
443 // Must come after the (et < SH7095_mem_timestamp) check.
444 if(MDFN_UNLIKELY(SH7095_BusLock))
445 return et + 1;
446
447 return CPU[c].DMA_Update(et);
448 }
449
450 //
451 //
452 //
453
InitEvents(void)454 static MDFN_COLD void InitEvents(void)
455 {
456 for(unsigned i = 0; i < SS_EVENT__COUNT; i++)
457 {
458 if(i == SS_EVENT__SYNFIRST)
459 events[i].event_time = 0;
460 else if(i == SS_EVENT__SYNLAST)
461 events[i].event_time = 0x7FFFFFFF;
462 else
463 events[i].event_time = 0; //SS_EVENT_DISABLED_TS;
464
465 events[i].prev = (i > 0) ? &events[i - 1] : NULL;
466 events[i].next = (i < (SS_EVENT__COUNT - 1)) ? &events[i + 1] : NULL;
467 }
468
469 events[SS_EVENT_SH2_M_DMA].event_handler = &SH_DMA_EventHandler<0>;
470 events[SS_EVENT_SH2_S_DMA].event_handler = &SH_DMA_EventHandler<1>;
471
472 events[SS_EVENT_SCU_DMA].event_handler = SCU_UpdateDMA;
473 events[SS_EVENT_SCU_DSP].event_handler = SCU_UpdateDSP;
474
475 events[SS_EVENT_SMPC].event_handler = SMPC_Update;
476
477 events[SS_EVENT_VDP1].event_handler = VDP1::Update;
478 events[SS_EVENT_VDP2].event_handler = VDP2::Update;
479
480 events[SS_EVENT_CDB].event_handler = CDB_Update;
481
482 events[SS_EVENT_SOUND].event_handler = SOUND_Update;
483
484 events[SS_EVENT_CART].event_handler = CART_GetEventHandler();
485
486 events[SS_EVENT_MIDSYNC].event_handler = MidSync;
487 events[SS_EVENT_MIDSYNC].event_time = SS_EVENT_DISABLED_TS;
488 }
489
RebaseTS(const sscpu_timestamp_t timestamp)490 static void RebaseTS(const sscpu_timestamp_t timestamp)
491 {
492 for(unsigned i = 0; i < SS_EVENT__COUNT; i++)
493 {
494 if(i == SS_EVENT__SYNFIRST || i == SS_EVENT__SYNLAST)
495 continue;
496
497 assert(events[i].event_time > timestamp);
498
499 if(events[i].event_time != SS_EVENT_DISABLED_TS)
500 events[i].event_time -= timestamp;
501 }
502
503 next_event_ts = events[SS_EVENT__SYNFIRST].next->event_time;
504 }
505
SS_SetEventNT(event_list_entry * e,const sscpu_timestamp_t next_timestamp)506 void SS_SetEventNT(event_list_entry* e, const sscpu_timestamp_t next_timestamp)
507 {
508 #ifdef MDFN_ENABLE_DEV_BUILD
509 if(next_timestamp < SS_EVENT_DISABLED_TS && (next_timestamp < 0 || next_timestamp >= 0x40000000))
510 {
511 fprintf(stderr, "Event %u, bad next timestamp 0x%08x\n", (unsigned)(e - events), next_timestamp);
512 abort();
513 }
514 #endif
515
516 if(next_timestamp < e->event_time)
517 {
518 event_list_entry *fe = e;
519
520 do
521 {
522 fe = fe->prev;
523 } while(next_timestamp < fe->event_time);
524
525 // Remove this event from the list, temporarily of course.
526 e->prev->next = e->next;
527 e->next->prev = e->prev;
528
529 // Insert into the list, just after "fe".
530 e->prev = fe;
531 e->next = fe->next;
532 fe->next->prev = e;
533 fe->next = e;
534
535 e->event_time = next_timestamp;
536 }
537 else if(next_timestamp > e->event_time)
538 {
539 event_list_entry *fe = e;
540
541 do
542 {
543 fe = fe->next;
544 } while(next_timestamp > fe->event_time);
545
546 // Remove this event from the list, temporarily of course
547 e->prev->next = e->next;
548 e->next->prev = e->prev;
549
550 // Insert into the list, just BEFORE "fe".
551 e->prev = fe->prev;
552 e->next = fe;
553 fe->prev->next = e;
554 fe->prev = e;
555
556 e->event_time = next_timestamp;
557 }
558
559 next_event_ts = ((Running > 0) ? events[SS_EVENT__SYNFIRST].next->event_time : 0);
560 }
561
562 // Called from debug.cpp too.
ForceEventUpdates(const sscpu_timestamp_t timestamp)563 void ForceEventUpdates(const sscpu_timestamp_t timestamp)
564 {
565 for(unsigned c = 0; c < 2; c++)
566 CPU[c].ForceInternalEventUpdates();
567
568 for(unsigned evnum = SS_EVENT__SYNFIRST + 1; evnum < SS_EVENT__SYNLAST; evnum++)
569 {
570 if(events[evnum].event_time != SS_EVENT_DISABLED_TS)
571 SS_SetEventNT(&events[evnum], events[evnum].event_handler(timestamp));
572 }
573
574 next_event_ts = ((Running > 0) ? events[SS_EVENT__SYNFIRST].next->event_time : 0);
575 }
576
EventHandler(const sscpu_timestamp_t timestamp)577 static INLINE bool EventHandler(const sscpu_timestamp_t timestamp)
578 {
579 event_list_entry *e;
580
581 while(timestamp >= (e = events[SS_EVENT__SYNFIRST].next)->event_time) // If Running = 0, EventHandler() may be called even if there isn't an event per-se, so while() instead of do { ... } while
582 {
583 #ifdef MDFN_ENABLE_DEV_BUILD
584 const sscpu_timestamp_t etime = e->event_time;
585 #endif
586 sscpu_timestamp_t nt;
587
588 nt = e->event_handler(e->event_time);
589
590 #ifdef MDFN_ENABLE_DEV_BUILD
591 if(MDFN_UNLIKELY(nt <= etime))
592 {
593 fprintf(stderr, "which=%d event_time=%d nt=%d timestamp=%d\n", (int)(e - events), etime, nt, timestamp);
594 assert(nt > etime);
595 }
596 #endif
597
598 SS_SetEventNT(e, nt);
599 }
600
601 return Running > 0;
602 }
603
CheckEventsByMemTS_Sub(void)604 static void NO_INLINE MDFN_HOT CheckEventsByMemTS_Sub(void)
605 {
606 EventHandler(SH7095_mem_timestamp);
607 }
608
CheckEventsByMemTS(void)609 static void INLINE CheckEventsByMemTS(void)
610 {
611 if(MDFN_UNLIKELY(SH7095_mem_timestamp >= next_event_ts))
612 {
613 //puts("Woot");
614 CheckEventsByMemTS_Sub();
615 }
616 }
617
SS_RequestEHLExit(void)618 void SS_RequestEHLExit(void)
619 {
620 if(Running)
621 {
622 Running = -1;
623 next_event_ts = 0;
624 }
625 }
626
SS_RequestMLExit(void)627 void SS_RequestMLExit(void)
628 {
629 Running = 0;
630 next_event_ts = 0;
631 }
632
633 #pragma GCC push_options
634 #pragma GCC optimize("O2,no-unroll-loops,no-peel-loops,no-crossjumping")
635 template<bool EmulateICache, bool DebugMode>
RunLoop_INLINE(EmulateSpecStruct * espec)636 static INLINE int32 RunLoop_INLINE(EmulateSpecStruct* espec)
637 {
638 sscpu_timestamp_t eff_ts = 0;
639
640 for(unsigned c = 0; c < 2; c++)
641 CPU[c].SetDebugMode(DebugMode);
642
643 //printf("%d %d\n", SH7095_mem_timestamp, CPU[0].timestamp);
644 do
645 {
646 SMPC_ProcessSlaveOffOn();
647 //
648 //
649 Running = true;
650 ForceEventUpdates(eff_ts);
651 do
652 {
653 do
654 {
655 if(DebugMode)
656 {
657 DBG_SetEffTS(eff_ts);
658 DBG_CPUHandler<0>();
659 }
660
661 CPU[0].Step<0, EmulateICache, DebugMode>();
662 CPU[0].DMA_BusTimingKludge();
663
664 if(EmulateICache)
665 {
666 if(DebugMode)
667 CPU[1].RunSlaveUntil_Debug(CPU[0].timestamp);
668 else
669 CPU[1].RunSlaveUntil(CPU[0].timestamp);
670 }
671 else
672 {
673 while(MDFN_LIKELY(CPU[0].timestamp > CPU[1].timestamp))
674 {
675 if(DebugMode)
676 DBG_CPUHandler<1>();
677
678 CPU[1].Step<1, false, DebugMode>();
679 }
680 }
681
682 eff_ts = CPU[0].timestamp;
683 if(SH7095_mem_timestamp > eff_ts)
684 eff_ts = SH7095_mem_timestamp;
685 else
686 SH7095_mem_timestamp = eff_ts;
687 } while(MDFN_LIKELY(eff_ts < next_event_ts));
688 } while(MDFN_LIKELY(EventHandler(eff_ts)));
689 } while(MDFN_LIKELY(Running != 0));
690
691 //printf(" End: %d %d -- %d\n", SH7095_mem_timestamp, CPU[0].timestamp, eff_ts);
692 return eff_ts;
693 }
694
695 template<bool EmulateICache>
RunLoop(EmulateSpecStruct * espec)696 static NO_INLINE MDFN_HOT int32 RunLoop(EmulateSpecStruct* espec)
697 {
698 return RunLoop_INLINE<EmulateICache, false>(espec);
699 }
700
701 template<bool EmulateICache>
RunLoop_Debug(EmulateSpecStruct * espec)702 static NO_INLINE MDFN_COLD int32 RunLoop_Debug(EmulateSpecStruct* espec)
703 {
704 return RunLoop_INLINE<EmulateICache, true>(espec);
705 }
706
707 #pragma GCC pop_options
708
709 // Must not be called within an event or read/write handler.
SS_Reset(bool powering_up)710 void SS_Reset(bool powering_up)
711 {
712 SH7095_BusLock = 0;
713
714 if(powering_up)
715 {
716 memset(WorkRAML, 0x00, sizeof(WorkRAML)); // TODO: Check
717 memset(WorkRAMH, 0x00, sizeof(WorkRAMH)); // TODO: Check
718 }
719
720 if(powering_up)
721 {
722 CPU[0].TruePowerOn();
723 CPU[1].TruePowerOn();
724 }
725
726 SCU_Reset(powering_up);
727 CPU[0].Reset(powering_up);
728
729 SMPC_Reset(powering_up);
730
731 VDP1::Reset(powering_up);
732 VDP2::Reset(powering_up);
733
734 CDB_Reset(powering_up);
735
736 SOUND_Reset(powering_up);
737
738 CART_Reset(powering_up);
739 }
740
741 static EmulateSpecStruct* espec;
742 static bool AllowMidSync;
743 static int32 cur_clock_div;
744
745 static int64 UpdateInputLastBigTS;
UpdateSMPCInput(const sscpu_timestamp_t timestamp)746 static INLINE void UpdateSMPCInput(const sscpu_timestamp_t timestamp)
747 {
748 int32 elapsed_time = (((int64)timestamp * cur_clock_div * 1000 * 1000) - UpdateInputLastBigTS) / (EmulatedSS.MasterClock / MDFN_MASTERCLOCK_FIXED(1));
749
750 UpdateInputLastBigTS += (int64)elapsed_time * (EmulatedSS.MasterClock / MDFN_MASTERCLOCK_FIXED(1));
751
752 SMPC_UpdateInput(elapsed_time);
753 }
754
MidSync(const sscpu_timestamp_t timestamp)755 static sscpu_timestamp_t MidSync(const sscpu_timestamp_t timestamp)
756 {
757 if(AllowMidSync)
758 {
759 //
760 // Don't call SOUND_Update() here, it's not necessary and will subtly alter emulation behavior from the perspective of the emulated program
761 // (which is not a problem in and of itself, but it's preferable to keep settings from altering emulation behavior when they don't need to).
762 //
763 //printf("MidSync: %d\n", VDP2::PeekLine());
764 if(!espec->NeedSoundReverse)
765 {
766 espec->SoundBufSize += SOUND_FlushOutput(espec->SoundBuf + (espec->SoundBufSize * 2), espec->SoundBufMaxSize - espec->SoundBufSize, espec->NeedSoundReverse);
767 espec->MasterCycles = timestamp * cur_clock_div;
768 }
769 //printf("%d\n", espec->SoundBufSize);
770
771 SMPC_UpdateOutput();
772 //
773 //
774 MDFN_MidSync(espec);
775 //
776 //
777 UpdateSMPCInput(timestamp);
778
779 AllowMidSync = false;
780 }
781
782 return SS_EVENT_DISABLED_TS;
783 }
784
Emulate(EmulateSpecStruct * espec_arg)785 static void Emulate(EmulateSpecStruct* espec_arg)
786 {
787 int32 end_ts;
788
789 espec = espec_arg;
790 AllowMidSync = true;
791 MDFNGameInfo->mouse_sensitivity = MDFN_GetSettingF("ss.input.mouse_sensitivity");
792
793 cur_clock_div = SMPC_StartFrame(espec);
794 UpdateSMPCInput(0);
795 VDP2::StartFrame(espec, cur_clock_div == 61);
796 SOUND_StartFrame(espec->SoundRate / espec->soundmultiplier, MDFN_GetSettingUI("ss.scsp.resamp_quality"));
797 CART_SetCPUClock(EmulatedSS.MasterClock / MDFN_MASTERCLOCK_FIXED(1), cur_clock_div);
798 espec->SoundBufSize = 0;
799 espec->MasterCycles = 0;
800 espec->soundmultiplier = 1;
801 //
802 //
803 //
804 #ifdef WANT_DEBUGGER
805 #define RLTDAT(eic) RunLoop_Debug<eic>
806 #else
807 #define RLTDAT(eic) RunLoop<eic>
808 #endif
809 static int32 (*const rltab[2][2])(EmulateSpecStruct*) =
810 {
811 //DebugMode=false DebugMode=true
812 { RunLoop<false>, RLTDAT(false) }, // EmulateICache=false
813 { RunLoop<true>, RLTDAT(true) }, // EmulateICache=true
814 };
815 #undef RLTDAT
816 end_ts = rltab[NeedEmuICache][DBG_NeedCPUHooks()](espec);
817 assert(end_ts >= 0);
818 ForceEventUpdates(end_ts);
819 //
820 SMPC_EndFrame(espec, end_ts);
821 //
822 //
823 //
824 RebaseTS(end_ts);
825
826 CDB_ResetTS();
827 SOUND_AdjustTS(-end_ts);
828 VDP1::AdjustTS(-end_ts);
829 VDP2::AdjustTS(-end_ts);
830 SMPC_ResetTS();
831 SCU_AdjustTS(-end_ts);
832 CART_AdjustTS(-end_ts);
833
834 UpdateInputLastBigTS -= (int64)end_ts * cur_clock_div * 1000 * 1000;
835 //
836 SH7095_mem_timestamp -= end_ts; // Update before CPU[n].AdjustTS()
837 //
838 for(unsigned c = 0; c < 2; c++)
839 CPU[c].AdjustTS(-end_ts);
840
841 //printf("B=% 7d M=% 7d S=% 7d\n", SH7095_mem_timestamp, CPU[0].timestamp, CPU[1].timestamp);
842 //
843 //
844 //
845 espec->MasterCycles = end_ts * cur_clock_div;
846 espec->SoundBufSize += SOUND_FlushOutput(espec->SoundBuf + (espec->SoundBufSize * 2), espec->SoundBufMaxSize - espec->SoundBufSize, espec->NeedSoundReverse);
847 espec->NeedSoundReverse = false;
848 //
849 //
850 //
851 SMPC_UpdateOutput();
852 //
853 //
854 //
855 if(BackupRAM_Dirty)
856 {
857 BackupRAM_SaveDelay = (int64)3 * (EmulatedSS.MasterClock / MDFN_MASTERCLOCK_FIXED(1)); // 3 second delay
858 BackupRAM_Dirty = false;
859 }
860 else if(BackupRAM_SaveDelay > 0)
861 {
862 BackupRAM_SaveDelay -= espec->MasterCycles;
863
864 if(BackupRAM_SaveDelay <= 0)
865 {
866 try
867 {
868 SaveBackupRAM();
869 }
870 catch(std::exception& e)
871 {
872 MDFND_OutputNotice(MDFN_NOTICE_ERROR, e.what());
873 BackupRAM_SaveDelay = (int64)60 * (EmulatedSS.MasterClock / MDFN_MASTERCLOCK_FIXED(1)); // 60 second retry delay.
874 }
875 }
876 }
877
878 if(CART_GetClearNVDirty())
879 CartNV_SaveDelay = (int64)3 * (EmulatedSS.MasterClock / MDFN_MASTERCLOCK_FIXED(1)); // 3 second delay
880 else if(CartNV_SaveDelay > 0)
881 {
882 CartNV_SaveDelay -= espec->MasterCycles;
883
884 if(CartNV_SaveDelay <= 0)
885 {
886 try
887 {
888 SaveCartNV();
889 }
890 catch(std::exception& e)
891 {
892 MDFND_OutputNotice(MDFN_NOTICE_ERROR, e.what());
893 CartNV_SaveDelay = (int64)60 * (EmulatedSS.MasterClock / MDFN_MASTERCLOCK_FIXED(1)); // 60 second retry delay.
894 }
895 }
896 }
897 }
898
899 //
900 //
901 //
902
Cleanup(void)903 static MDFN_COLD void Cleanup(void)
904 {
905 CART_Kill();
906
907 DBG_Kill();
908 VDP1::Kill();
909 VDP2::Kill();
910 SOUND_Kill();
911 CDB_Kill();
912
913 cdifs = NULL;
914 }
915
IsSaturnDisc(const uint8 * sa32k)916 static MDFN_COLD bool IsSaturnDisc(const uint8* sa32k)
917 {
918 if(sha256(&sa32k[0x100], 0xD00) != "96b8ea48819cfa589f24c40aa149c224c420dccf38b730f00156efe25c9bbc8f"_sha256)
919 return false;
920
921 if(memcmp(&sa32k[0], "SEGA SEGASATURN ", 16))
922 return false;
923
924 return true;
925 }
926
CalcGameID(uint8 * id_out16,uint8 * fd_id_out16,char * sgid,char * sgname,char * sgarea)927 static INLINE void CalcGameID(uint8* id_out16, uint8* fd_id_out16, char* sgid, char* sgname, char* sgarea)
928 {
929 std::unique_ptr<uint8[]> buf(new uint8[2048]);
930 md5_context mctx;
931
932 mctx.starts();
933
934 for(size_t x = 0; x < cdifs->size(); x++)
935 {
936 auto* c = (*cdifs)[x];
937 CDUtility::TOC toc;
938
939 c->ReadTOC(&toc);
940
941 mctx.update_u32_as_lsb(toc.first_track);
942 mctx.update_u32_as_lsb(toc.last_track);
943 mctx.update_u32_as_lsb(toc.disc_type);
944
945 for(unsigned i = 1; i <= 100; i++)
946 {
947 const auto& t = toc.tracks[i];
948
949 mctx.update_u32_as_lsb(t.adr);
950 mctx.update_u32_as_lsb(t.control);
951 mctx.update_u32_as_lsb(t.lba);
952 mctx.update_u32_as_lsb(t.valid);
953 }
954
955 for(unsigned i = 0; i < 512; i++)
956 {
957 if(c->ReadSectors(&buf[0], i, 1) >= 0x1)
958 {
959 if(i == 0)
960 {
961 char* tmp;
962 memcpy(sgid, &buf[0x20], 16);
963 sgid[16] = 0;
964 if((tmp = strrchr(sgid, 'V')))
965 {
966 do
967 {
968 *tmp = 0;
969 } while(tmp-- != sgid && (signed char)*tmp <= 0x20);
970 }
971 memcpy(sgname, &buf[0x60], 0x70);
972 sgname[0x70] = 0;
973 MDFN_zapctrlchars(sgname);
974 MDFN_trim(sgname);
975
976 memcpy(sgarea, &buf[0x40], 0x10);
977 sgarea[0x10] = 0;
978 MDFN_zapctrlchars(sgarea);
979 MDFN_trim(sgarea);
980 }
981
982 mctx.update(&buf[0], 2048);
983 }
984 }
985
986 if(x == 0)
987 {
988 md5_context fd_mctx = mctx;
989 fd_mctx.finish(fd_id_out16);
990 }
991 }
992
993 mctx.finish(id_out16);
994 }
995
996 //
997 // Remember to rebuild region database in db.cpp if changing the order of entries in this table(and be careful about game id collisions, e.g. with some Korean games).
998 //
999 static const struct
1000 {
1001 const char c;
1002 const char* str; // Community-defined region string that may appear in filename.
1003 unsigned region;
1004 } region_strings[] =
1005 {
1006 // Listed in order of preference for multi-region games.
1007 { 'U', "USA", SMPC_AREA_NA },
1008 { 'J', "Japan", SMPC_AREA_JP },
1009 { 'K', "Korea", SMPC_AREA_KR },
1010
1011 { 'E', "Europe", SMPC_AREA_EU_PAL },
1012 { 'E', "Germany", SMPC_AREA_EU_PAL },
1013 { 'E', "France", SMPC_AREA_EU_PAL },
1014 { 'E', "Spain", SMPC_AREA_EU_PAL },
1015
1016 { 'B', "Brazil", SMPC_AREA_CSA_NTSC },
1017
1018 { 'T', nullptr, SMPC_AREA_ASIA_NTSC },
1019 { 'A', nullptr, SMPC_AREA_ASIA_PAL },
1020 { 'L', nullptr, SMPC_AREA_CSA_PAL },
1021 };
1022
DetectRegion(unsigned * const region)1023 static INLINE bool DetectRegion(unsigned* const region)
1024 {
1025 std::unique_ptr<uint8[]> buf(new uint8[2048 * 16]);
1026 uint64 possible_regions = 0;
1027
1028 for(auto& c : *cdifs)
1029 {
1030 if(c->ReadSectors(&buf[0], 0, 16) != 0x1)
1031 continue;
1032
1033 if(!IsSaturnDisc(&buf[0]))
1034 continue;
1035
1036 for(unsigned i = 0; i < 16; i++)
1037 {
1038 for(auto const& rs : region_strings)
1039 {
1040 if(rs.c == buf[0x40 + i])
1041 {
1042 possible_regions |= (uint64)1 << rs.region;
1043 break;
1044 }
1045 }
1046 }
1047 break;
1048 }
1049
1050 for(auto const& rs : region_strings)
1051 {
1052 if(possible_regions & ((uint64)1 << rs.region))
1053 {
1054 *region = rs.region;
1055 return true;
1056 }
1057 }
1058
1059 return false;
1060 }
1061 #if 0
1062 static MDFN_COLD bool DetectRegionByFN(const std::string& fn, unsigned* const region)
1063 {
1064 std::string ss = fn;
1065 size_t cp_pos;
1066 uint64 possible_regions = 0;
1067
1068 while((cp_pos = ss.rfind(')')) != std::string::npos && cp_pos > 0)
1069 {
1070 ss.resize(cp_pos);
1071 //
1072 size_t op_pos = ss.rfind('(');
1073
1074 if(op_pos != std::string::npos)
1075 {
1076 for(auto const& rs : region_strings)
1077 {
1078 if(!rs.str)
1079 continue;
1080
1081 size_t rs_pos = ss.find(rs.str, op_pos + 1);
1082
1083 if(rs_pos != std::string::npos)
1084 {
1085 bool leading_ok = true;
1086 bool trailing_ok = true;
1087
1088 for(size_t i = rs_pos - 1; i > op_pos; i--)
1089 {
1090 if(ss[i] == ',')
1091 break;
1092 else if(ss[i] != ' ')
1093 {
1094 leading_ok = false;
1095 break;
1096 }
1097 }
1098
1099 for(size_t i = rs_pos + strlen(rs.str); i < ss.size(); i++)
1100 {
1101 if(ss[i] == ',')
1102 break;
1103 else if(ss[i] != ' ')
1104 {
1105 trailing_ok = false;
1106 break;
1107 }
1108 }
1109
1110 if(leading_ok && trailing_ok)
1111 possible_regions |= (uint64)1 << rs.region;
1112 }
1113 }
1114 }
1115 }
1116
1117 for(auto const& rs : region_strings)
1118 {
1119 if(possible_regions & ((uint64)1 << rs.region))
1120 {
1121 *region = rs.region;
1122 return true;
1123 }
1124 }
1125
1126 return false;
1127 }
1128 #endif
InitCommon(const unsigned cpucache_emumode,const unsigned horrible_hacks,const unsigned cart_type,const unsigned smpc_area,Stream * dbg_cart_rom_stream)1129 static void MDFN_COLD InitCommon(const unsigned cpucache_emumode, const unsigned horrible_hacks, const unsigned cart_type, const unsigned smpc_area, Stream* dbg_cart_rom_stream)
1130 {
1131 const char* cart_rom_path_sname = nullptr;
1132
1133 #ifdef MDFN_ENABLE_DEV_BUILD
1134 ss_dbg_mask = SS_DBG_ERROR;
1135 {
1136 std::vector<uint64> dms = MDFN_GetSettingMultiUI("ss.dbg_mask");
1137
1138 for(uint64 dmse : dms)
1139 ss_dbg_mask |= dmse;
1140 }
1141
1142 static const uint32 addrs[] =
1143 {
1144 0x280, 0x300, 0x304, 0x308, 0x30C, 0x310, 0x314, 0x318, 0x31C, 0x320, 0x324, 0x330, 0x334, 0x340, 0x344, 0x348,
1145 0x34C, 0x354, 0x358
1146 };
1147
1148 static const uint32 wr_addrs[] = { 0x250, 0x348 };
1149
1150 for(size_t i = 0; i < sizeof(addrs) / sizeof(addrs[0]); i++)
1151 BWMIgnoreAddr[0][addrs[i] & 0x1FF] = true;
1152
1153 for(size_t i = 0; i < sizeof(wr_addrs) / sizeof(wr_addrs[0]); i++)
1154 BWMIgnoreAddr[1][wr_addrs[i] & 0x1FF] = true;
1155 #endif
1156
1157 //
1158 {
1159 const struct
1160 {
1161 unsigned mode;
1162 const char* name;
1163 } CPUCacheEmuModes[] =
1164 {
1165 { CPUCACHE_EMUMODE_DATA_CB, _("Data only, with high-level bypass") },
1166 { CPUCACHE_EMUMODE_DATA, _("Data only") },
1167 { CPUCACHE_EMUMODE_FULL, _("Full") },
1168 };
1169 const char* cem = _("Unknown");
1170
1171 for(auto const& ceme : CPUCacheEmuModes)
1172 {
1173 if(ceme.mode == cpucache_emumode)
1174 {
1175 cem = ceme.name;
1176 break;
1177 }
1178 }
1179 MDFN_printf(_("CPU Cache Emulation Mode: %s\n"), cem);
1180 }
1181 //
1182 if(horrible_hacks)
1183 MDFN_printf(_("Horrible hacks: %s\n"), DB_GetHHDescriptions(horrible_hacks).c_str());
1184 //
1185 {
1186 MDFN_printf(_("Region: 0x%01x\n"), smpc_area);
1187 const struct
1188 {
1189 const unsigned type;
1190 const char* name;
1191 const char* rom_path_sname;
1192 } CartNames[] =
1193 {
1194 { CART_NONE, _("None"), nullptr },
1195 { CART_BACKUP_MEM, _("Backup Memory"), nullptr },
1196 { CART_EXTRAM_1M, _("1MiB Extended RAM"), nullptr },
1197 { CART_EXTRAM_4M, _("4MiB Extended RAM"), nullptr },
1198 { CART_KOF95, _("King of Fighters '95 ROM"), "ss.cart.kof95_path" },
1199 { CART_ULTRAMAN, _("Ultraman ROM"), "ss.cart.ultraman_path" },
1200 { CART_AR4MP, _("Action Replay 4M Plus"), "ss.cart.satar4mp_path" },
1201 { CART_CS1RAM_16M, _("16MiB CS1 RAM"), nullptr },
1202 { CART_NLMODEM, _("Netlink Modem"), nullptr },
1203 { CART_MDFN_DEBUG, _("Mednafen Debug"), nullptr },
1204 };
1205 const char* cn = _("Unknown");
1206
1207 for(auto const& cne : CartNames)
1208 {
1209 if(cne.type == cart_type)
1210 {
1211 cn = cne.name;
1212 cart_rom_path_sname = cne.rom_path_sname;
1213 break;
1214 }
1215 }
1216 MDFN_printf(_("Cart: %s\n"), cn);
1217 }
1218 //
1219 NeedEmuICache = (cpucache_emumode == CPUCACHE_EMUMODE_FULL);
1220 for(unsigned c = 0; c < 2; c++)
1221 {
1222 CPU[c].Init((cpucache_emumode == CPUCACHE_EMUMODE_FULL), (cpucache_emumode == CPUCACHE_EMUMODE_DATA_CB));
1223 CPU[c].SetMD5((bool)c);
1224 }
1225 SH7095_mem_timestamp = 0;
1226 SH7095_DB = 0;
1227
1228 ss_horrible_hacks = horrible_hacks;
1229
1230 //
1231 // Initialize backup memory.
1232 //
1233 memset(BackupRAM, 0x00, sizeof(BackupRAM));
1234 for(unsigned i = 0; i < 0x40; i++)
1235 BackupRAM[i] = BRAM_Init_Data[i & 0x0F];
1236
1237 // Call InitFastMemMap() before functions like SOUND_Init()
1238 InitFastMemMap();
1239 SS_SetPhysMemMap(0x00000000, 0x000FFFFF, BIOSROM, sizeof(BIOSROM));
1240 SS_SetPhysMemMap(0x00200000, 0x003FFFFF, WorkRAML, sizeof(WorkRAML), true);
1241 SS_SetPhysMemMap(0x06000000, 0x07FFFFFF, WorkRAMH, sizeof(WorkRAMH), true);
1242 MDFNMP_RegSearchable(0x00200000, sizeof(WorkRAML));
1243 MDFNMP_RegSearchable(0x06000000, sizeof(WorkRAMH));
1244
1245 {
1246 std::unique_ptr<FileStream> cart_rom_stream;
1247
1248 if(cart_rom_path_sname)
1249 {
1250 const std::string cart_rom_path = MDFN_MakeFName(MDFNMKF_FIRMWARE, 0, MDFN_GetSettingS(cart_rom_path_sname));
1251
1252 cart_rom_stream.reset(new FileStream(cart_rom_path, FileStream::MODE_READ));
1253 }
1254
1255 CART_Init(cart_type, cart_rom_stream ? cart_rom_stream.get() : dbg_cart_rom_stream);
1256 ActiveCartType = cart_type;
1257 }
1258 //
1259 //
1260 //
1261 const bool PAL = (smpc_area & SMPC_AREA__PAL_MASK);
1262 const int32 MasterClock = PAL ? 1734687500 : 1746818182; // NTSC: 1746818181.8181818181, PAL: 1734687500-ish
1263 const char* biospath_sname;
1264 int sls = MDFN_GetSettingI(PAL ? "ss.slstartp" : "ss.slstart");
1265 int sle = MDFN_GetSettingI(PAL ? "ss.slendp" : "ss.slend");
1266 const uint64 vdp2_affinity = MDFN_GetSettingUI("ss.affinity.vdp2");
1267
1268 if(PAL)
1269 {
1270 sls += 16;
1271 sle += 16;
1272 }
1273
1274 if(sls > sle)
1275 std::swap(sls, sle);
1276
1277 if(smpc_area == SMPC_AREA_JP || smpc_area == SMPC_AREA_ASIA_NTSC)
1278 biospath_sname = "ss.bios_jp";
1279 else
1280 biospath_sname = "ss.bios_na_eu";
1281
1282 {
1283 const std::string biospath = MDFN_MakeFName(MDFNMKF_FIRMWARE, 0, MDFN_GetSettingS(biospath_sname));
1284 FileStream BIOSFile(biospath, FileStream::MODE_READ);
1285
1286 if(BIOSFile.size() != 524288)
1287 throw MDFN_Error(0, _("BIOS file \"%s\" is of an incorrect size."), biospath.c_str());
1288
1289 BIOSFile.read(BIOSROM, 512 * 1024);
1290 BIOS_SHA256 = sha256(BIOSROM, 512 * 1024);
1291
1292 if(MDFN_GetSettingB("ss.bios_sanity"))
1293 {
1294 static const struct
1295 {
1296 const char* fn;
1297 sha256_digest hash;
1298 const uint32 areas;
1299 } BIOSDB[] =
1300 {
1301 { "sega1003.bin", "cc1e1b7f88f1c6e6fc35994bae2c2292e06fdae258c79eb26a1f1391e72914a8"_sha256, (1U << SMPC_AREA_JP) | (1U << SMPC_AREA_ASIA_NTSC), },
1302 { "sega_100.bin", "ae4058627bb5db9be6d8d83c6be95a4aa981acc8a89042e517e73317886c8bc2"_sha256, (1U << SMPC_AREA_JP) | (1U << SMPC_AREA_ASIA_NTSC), },
1303 { "sega_101.bin", "dcfef4b99605f872b6c3b6d05c045385cdea3d1b702906a0ed930df7bcb7deac"_sha256, (1U << SMPC_AREA_JP) | (1U << SMPC_AREA_ASIA_NTSC), },
1304 { "sega_100a.bin", "87293093fad802fcff31fcab427a16caff1acbc5184899b8383b360fd58efb73"_sha256, (~0U) & ~((1U << SMPC_AREA_JP) | (1U << SMPC_AREA_ASIA_NTSC)) },
1305 { "mpr-17933.bin", "96e106f740ab448cf89f0dd49dfbac7fe5391cb6bd6e14ad5e3061c13330266f"_sha256, (~0U) & ~((1U << SMPC_AREA_JP) | (1U << SMPC_AREA_ASIA_NTSC)) },
1306 };
1307 std::string fnbase, fnext;
1308 std::string fn;
1309
1310 NVFS.get_file_path_components(biospath, nullptr, &fnbase, &fnext);
1311 fn = fnbase + fnext;
1312
1313 // Discourage people from renaming files instead of changing settings.
1314 for(auto const& dbe : BIOSDB)
1315 {
1316 if(fn == dbe.fn && BIOS_SHA256 != dbe.hash)
1317 throw MDFN_Error(0, _("The BIOS ROM data loaded from \"%s\" does not match what is expected by its filename(possibly due to erroneous file renaming by the user)."), biospath.c_str());
1318 }
1319
1320 for(auto const& dbe : BIOSDB)
1321 {
1322 if(BIOS_SHA256 == dbe.hash && !(dbe.areas & (1U << smpc_area)))
1323 throw MDFN_Error(0, _("The BIOS loaded from \"%s\" is the wrong BIOS for the region being emulated(possibly due to changing setting \"%s\" to point to the wrong file)."), biospath.c_str(), biospath_sname);
1324 }
1325 }
1326 //
1327 //
1328 for(unsigned i = 0; i < 262144; i++)
1329 BIOSROM[i] = MDFN_de16msb(&BIOSROM[i]);
1330 }
1331
1332 EmulatedSS.MasterClock = MDFN_MASTERCLOCK_FIXED(MasterClock);
1333
1334 SCU_Init();
1335 SMPC_Init(smpc_area, MasterClock);
1336 VDP1::Init();
1337 VDP2::Init(PAL, vdp2_affinity);
1338 CDB_Init();
1339 SOUND_Init();
1340
1341 InitEvents();
1342 UpdateInputLastBigTS = 0;
1343
1344 DBG_Init();
1345 //
1346 //
1347 //
1348 MDFN_printf("\n");
1349 {
1350 const bool correct_aspect = MDFN_GetSettingB("ss.correct_aspect");
1351 const bool h_overscan = MDFN_GetSettingB("ss.h_overscan");
1352 const bool h_blend = MDFN_GetSettingB("ss.h_blend");
1353
1354 MDFN_printf(_("Displayed scanlines: [%u,%u]\n"), sls, sle);
1355 MDFN_printf(_("Correct Aspect Ratio: %s\n"), correct_aspect ? _("Enabled") : _("Disabled"));
1356 MDFN_printf(_("Show H Overscan: %s\n"), h_overscan ? _("Enabled") : _("Disabled"));
1357 MDFN_printf(_("H Blend: %s\n"), h_blend ? _("Enabled") : _("Disabled"));
1358
1359 VDP2::SetGetVideoParams(&EmulatedSS, correct_aspect, sls, sle, h_overscan, h_blend);
1360 }
1361
1362 MDFN_printf("\n");
1363 for(unsigned sp = 0; sp < 2; sp++)
1364 {
1365 char buf[64];
1366 bool sv;
1367
1368 trio_snprintf(buf, sizeof(buf), "ss.input.sport%u.multitap", sp + 1);
1369 sv = MDFN_GetSettingB(buf);
1370 SMPC_SetMultitap(sp, sv);
1371
1372 MDFN_printf(_("Multitap on Saturn Port %u: %s\n"), sp + 1, sv ? _("Enabled") : _("Disabled"));
1373 }
1374
1375 for(unsigned vp = 0; vp < 12; vp++)
1376 {
1377 char buf[64];
1378 uint32 sv;
1379
1380 trio_snprintf(buf, sizeof(buf), "ss.input.port%u.gun_chairs", vp + 1);
1381 sv = MDFN_GetSettingUI(buf);
1382 SMPC_SetCrosshairsColor(vp, sv);
1383 }
1384
1385 //
1386 //
1387 //
1388 try { LoadRTC(); } catch(MDFN_Error& e) { if(e.GetErrno() != ENOENT) throw; }
1389 try { LoadBackupRAM(); } catch(MDFN_Error& e) { if(e.GetErrno() != ENOENT) throw; }
1390 try { LoadCartNV(); } catch(MDFN_Error& e) { if(e.GetErrno() != ENOENT) throw; }
1391
1392 BackupBackupRAM();
1393 BackupCartNV();
1394
1395 BackupRAM_Dirty = false;
1396 BackupRAM_SaveDelay = 0;
1397
1398 CART_GetClearNVDirty();
1399 CartNV_SaveDelay = 0;
1400 //
1401 if(MDFN_GetSettingB("ss.smpc.autortc"))
1402 {
1403 struct tm ht = Time::LocalTime();
1404
1405 SMPC_SetRTC(&ht, MDFN_GetSettingUI("ss.smpc.autortc.lang"));
1406 }
1407 //
1408 SS_Reset(true);
1409 }
1410
TestMagic(GameFile * gf)1411 static MDFN_COLD bool TestMagic(GameFile* gf)
1412 {
1413 if(gf->ext == "ss")
1414 return true;
1415
1416 return false;
1417 }
1418
Load(GameFile * gf)1419 static MDFN_COLD void Load(GameFile* gf)
1420 {
1421 #if 0
1422 // cat regiondb.inc | sort | uniq --all-repeated=separate -w 102
1423 {
1424 FileStream rdbfp("/tmp/regiondb.inc", FileStream::MODE_WRITE);
1425 Stream* s = fp->stream();
1426 std::string linebuf;
1427 static std::vector<CDInterface*> CDInterfaces;
1428
1429 cdifs = &CDInterfaces;
1430
1431 while(s->get_line(linebuf) >= 0)
1432 {
1433 static uint8 sbuf[2048 * 16];
1434 CDInterface* iface = CDInterface::Open(linebuf, false);
1435 int m = iface->ReadSectors(sbuf, 0, 16);
1436 std::string fb;
1437
1438 assert(m == 0x1);
1439 assert(IsSaturnDisc(&sbuf[0]) == true);
1440 //
1441 uint8 dummytmp[16] = { 0 };
1442 uint8 tmp[16] = { 0 };
1443 const char* regstr;
1444 unsigned region = ~0U;
1445
1446 NVFS.get_file_path_components(linebuf, nullptr, &fb);
1447
1448 if(!DetectRegionByFN(fb, ®ion))
1449 abort();
1450
1451 switch(region)
1452 {
1453 default: abort(); break;
1454 case SMPC_AREA_NA: regstr = "SMPC_AREA_NA"; break;
1455 case SMPC_AREA_JP: regstr = "SMPC_AREA_JP"; break;
1456 case SMPC_AREA_EU_PAL: regstr = "SMPC_AREA_EU_PAL"; break;
1457 case SMPC_AREA_KR: regstr = "SMPC_AREA_KR"; break;
1458 case SMPC_AREA_CSA_NTSC: regstr = "SMPC_AREA_CSA_NTSC"; break;
1459 }
1460
1461 CDInterfaces.clear();
1462 CDInterfaces.push_back(iface);
1463
1464 CalcGameID(dummytmp, tmp);
1465
1466 unsigned tmpreg;
1467 if(!DetectRegion(&tmpreg) || tmpreg != region)
1468 {
1469 rdbfp.print_format("{ { ");
1470 for(unsigned i = 0; i < 16; i++)
1471 rdbfp.print_format("0x%02x, ", tmp[i]);
1472 rdbfp.print_format("}, %s }, // %s\n", regstr, fb.c_str());
1473 }
1474
1475 delete iface;
1476 }
1477 }
1478
1479 return;
1480 #endif
1481
1482 cdifs = NULL;
1483
1484 try
1485 {
1486 if(MDFN_GetSettingS("ss.dbg_exe_cdpath") != "")
1487 {
1488 RMD_Drive dr;
1489 RMD_DriveDefaults drdef;
1490
1491 dr.Name = std::string("Virtual CD Drive");
1492 dr.PossibleStates.push_back(RMD_State({"Tray Open", false, false, true}));
1493 dr.PossibleStates.push_back(RMD_State({"Tray Closed (Empty)", false, false, false}));
1494 dr.PossibleStates.push_back(RMD_State({"Tray Closed", true, true, false}));
1495 dr.CompatibleMedia.push_back(0);
1496 dr.MediaMtoPDelay = 2000;
1497
1498 drdef.State = 2; // Tray Closed
1499 drdef.Media = 0;
1500 drdef.Orientation = 0;
1501
1502 MDFNGameInfo->RMD->Drives.push_back(dr);
1503 MDFNGameInfo->RMD->DrivesDefaults.push_back(drdef);
1504 MDFNGameInfo->RMD->MediaTypes.push_back(RMD_MediaType({"CD"}));
1505 MDFNGameInfo->RMD->Media.push_back(RMD_Media({"Test CD", 0}));
1506
1507 static std::vector<CDInterface*> CDInterfaces;
1508 CDInterfaces.clear();
1509 CDInterfaces.push_back(CDInterface::Open(&NVFS, MDFN_GetSettingS("ss.dbg_exe_cdpath"), false, MDFN_GetSettingUI("affinity.cd")));
1510 cdifs = &CDInterfaces;
1511 }
1512 //
1513 //
1514 uint32 horrible_hacks = 0;
1515 {
1516 std::vector<uint64> dhhs = MDFN_GetSettingMultiUI("ss.dbg_exe_hh");
1517
1518 for(uint64 dhhse : dhhs)
1519 horrible_hacks |= dhhse;
1520 }
1521
1522 InitCommon(MDFN_GetSettingUI("ss.dbg_exe_cem"), horrible_hacks, CART_MDFN_DEBUG, MDFN_GetSettingUI("ss.region_default"), gf->stream);
1523 }
1524 catch(...)
1525 {
1526 Cleanup();
1527 throw;
1528 }
1529 }
1530
TestMagicCD(std::vector<CDInterface * > * CDInterfaces)1531 static MDFN_COLD bool TestMagicCD(std::vector<CDInterface*> *CDInterfaces)
1532 {
1533 std::unique_ptr<uint8[]> buf(new uint8[2048 * 16]);
1534
1535 if((*CDInterfaces)[0]->ReadSectors(&buf[0], 0, 16) != 0x1)
1536 return false;
1537
1538 return IsSaturnDisc(&buf[0]);
1539 }
1540
DiscSanityChecks(void)1541 static MDFN_COLD void DiscSanityChecks(void)
1542 {
1543 for(size_t i = 0; i < cdifs->size(); i++)
1544 {
1545 CDUtility::TOC toc;
1546
1547 (*cdifs)[i]->ReadTOC(&toc);
1548
1549 for(int32 track = 1; track <= 99; track++)
1550 {
1551 if(!toc.tracks[track].valid)
1552 continue;
1553
1554 if(toc.tracks[track].control & CDUtility::SUBQ_CTRLF_DATA)
1555 continue;
1556 //
1557 //
1558 //
1559 const int32 start_lba = toc.tracks[track].lba;
1560 const int32 end_lba = start_lba + 32 - 1;
1561 bool any_subq_curpos = false;
1562
1563 for(int32 lba = start_lba; lba <= end_lba; lba++)
1564 {
1565 uint8 pwbuf[96];
1566 uint8 qbuf[12];
1567
1568 if(!(*cdifs)[i]->ReadRawSectorPWOnly(pwbuf, lba, false))
1569 throw MDFN_Error(0, _("Disc %zu of %zu: Error reading sector at lba=%d in DiscSanityChecks()."), i + 1, cdifs->size(), lba);
1570
1571 CDUtility::subq_deinterleave(pwbuf, qbuf);
1572 if(CDUtility::subq_check_checksum(qbuf) && (qbuf[0] & 0xF) == CDUtility::ADR_CURPOS)
1573 {
1574 const uint8 qm = qbuf[7];
1575 const uint8 qs = qbuf[8];
1576 const uint8 qf = qbuf[9];
1577 uint8 lm, ls, lf;
1578
1579 any_subq_curpos = true;
1580
1581 CDUtility::LBA_to_AMSF(lba, &lm, &ls, &lf);
1582 lm = CDUtility::U8_to_BCD(lm);
1583 ls = CDUtility::U8_to_BCD(ls);
1584 lf = CDUtility::U8_to_BCD(lf);
1585
1586 if(lm != qm || ls != qs || lf != qf)
1587 {
1588 throw MDFN_Error(0, _("Disc %zu of %zu: Time mismatch at lba=%d(%02x:%02x:%02x); Q subchannel: %02x:%02x:%02x"),
1589 i + 1, cdifs->size(),
1590 lba,
1591 lm, ls, lf,
1592 qm, qs, qf);
1593 }
1594 }
1595 }
1596
1597 if(!any_subq_curpos)
1598 {
1599 throw MDFN_Error(0, _("Disc %zu of %zu: No valid Q subchannel ADR_CURPOS data present at lba %d-%d?!"), i + 1, cdifs->size(), start_lba, end_lba);
1600 }
1601
1602 break;
1603 }
1604 }
1605 }
1606
LoadCD(std::vector<CDInterface * > * CDInterfaces)1607 static MDFN_COLD void LoadCD(std::vector<CDInterface*>* CDInterfaces)
1608 {
1609 try
1610 {
1611 const int ss_cart_setting = MDFN_GetSettingI("ss.cart");
1612 const unsigned region_default = MDFN_GetSettingI("ss.region_default");
1613 unsigned region;
1614 int cart_type;
1615 unsigned cpucache_emumode;
1616 unsigned horrible_hacks;
1617 uint8 fd_id[16];
1618 char sgid[16 + 1] = { 0 };
1619 char sgname[0x70 + 1] = { 0 };
1620 char sgarea[0x10 + 1] = { 0 };
1621 cdifs = CDInterfaces;
1622 CalcGameID(MDFNGameInfo->MD5, fd_id, sgid, sgname, sgarea);
1623
1624 MDFN_printf("SGID: %s\n", sgid);
1625 MDFN_printf("SGNAME: %s\n", sgname);
1626 MDFN_printf("SGAREA: %s\n", sgarea);
1627
1628 region = region_default;
1629 cart_type = MDFN_GetSettingI("ss.cart.auto_default");
1630 cpucache_emumode = CPUCACHE_EMUMODE_DATA;
1631
1632 DetectRegion(®ion);
1633 DB_Lookup(nullptr, sgid, sgname, sgarea, fd_id, ®ion, &cart_type, &cpucache_emumode);
1634 horrible_hacks = DB_LookupHH(sgid, fd_id);
1635 //
1636 if(!MDFN_GetSettingB("ss.region_autodetect"))
1637 region = region_default;
1638
1639 if(ss_cart_setting != CART__RESERVED)
1640 cart_type = ss_cart_setting;
1641 //
1642 if(MDFN_GetSettingB("ss.cd_sanity"))
1643 DiscSanityChecks();
1644 else
1645 MDFN_printf(_("WARNING: CD (image) sanity checks disabled."));
1646
1647 // TODO: auth ID calc
1648
1649 InitCommon(cpucache_emumode, horrible_hacks, cart_type, region, nullptr);
1650 }
1651 catch(...)
1652 {
1653 Cleanup();
1654 throw;
1655 }
1656 }
1657
CloseGame(void)1658 static MDFN_COLD void CloseGame(void)
1659 {
1660 #ifdef MDFN_ENABLE_DEV_BUILD
1661 VDP1::MakeDump("/tmp/vdp1_dump.h");
1662 VDP2::MakeDump("/tmp/vdp2_dump.h");
1663 #endif
1664 //
1665 //
1666
1667 try { SaveBackupRAM(); } catch(std::exception& e) { MDFND_OutputNotice(MDFN_NOTICE_ERROR, e.what()); }
1668 try { SaveCartNV(); } catch(std::exception& e) { MDFND_OutputNotice(MDFN_NOTICE_ERROR, e.what()); }
1669 try { SaveRTC(); } catch(std::exception& e) { MDFND_OutputNotice(MDFN_NOTICE_ERROR, e.what()); }
1670
1671 Cleanup();
1672 }
1673
SaveBackupRAM(void)1674 static MDFN_COLD void SaveBackupRAM(void)
1675 {
1676 FileStream brs(MDFN_MakeFName(MDFNMKF_SAV, 0, "bkr"), FileStream::MODE_WRITE_INPLACE);
1677
1678 brs.write(BackupRAM, sizeof(BackupRAM));
1679
1680 brs.close();
1681 }
1682
LoadBackupRAM(void)1683 static MDFN_COLD void LoadBackupRAM(void)
1684 {
1685 FileStream brs(MDFN_MakeFName(MDFNMKF_SAV, 0, "bkr"), FileStream::MODE_READ);
1686
1687 brs.read(BackupRAM, sizeof(BackupRAM));
1688 }
1689
BackupBackupRAM(void)1690 static MDFN_COLD void BackupBackupRAM(void)
1691 {
1692 MDFN_BackupSavFile(10, "bkr");
1693 }
1694
BackupCartNV(void)1695 static MDFN_COLD void BackupCartNV(void)
1696 {
1697 const char* ext = nullptr;
1698 void* nv_ptr = nullptr;
1699 bool nv16 = false;
1700 uint64 nv_size = 0;
1701
1702 CART_GetNVInfo(&ext, &nv_ptr, &nv16, &nv_size);
1703
1704 if(ext)
1705 MDFN_BackupSavFile(10, ext);
1706 }
1707
LoadCartNV(void)1708 static MDFN_COLD void LoadCartNV(void)
1709 {
1710 const char* ext = nullptr;
1711 void* nv_ptr = nullptr;
1712 bool nv16 = false;
1713 uint64 nv_size = 0;
1714
1715 CART_GetNVInfo(&ext, &nv_ptr, &nv16, &nv_size);
1716
1717 if(ext)
1718 {
1719 //FileStream nvs(MDFN_MakeFName(MDFNMKF_SAV, 0, ext), FileStream::MODE_READ);
1720 GZFileStream nvs(MDFN_MakeFName(MDFNMKF_SAV, 0, ext), GZFileStream::MODE::READ);
1721
1722 nvs.read(nv_ptr, nv_size);
1723
1724 if(nv16)
1725 {
1726 for(uint64 i = 0; i < nv_size; i += 2)
1727 {
1728 void* p = (uint8*)nv_ptr + i;
1729
1730 MDFN_ennsb<uint16>(p, MDFN_de16msb(p));
1731 }
1732 }
1733 }
1734 }
1735
SaveCartNV(void)1736 static MDFN_COLD void SaveCartNV(void)
1737 {
1738 const char* ext = nullptr;
1739 void* nv_ptr = nullptr;
1740 bool nv16 = false;
1741 uint64 nv_size = 0;
1742
1743 CART_GetNVInfo(&ext, &nv_ptr, &nv16, &nv_size);
1744
1745 if(ext)
1746 {
1747 //FileStream nvs(MDFN_MakeFName(MDFNMKF_SAV, 0, ext), FileStream::MODE_WRITE_INPLACE);
1748 GZFileStream nvs(MDFN_MakeFName(MDFNMKF_SAV, 0, ext), GZFileStream::MODE::WRITE);
1749
1750 if(nv16)
1751 {
1752 // Slow...
1753 for(uint64 i = 0; i < nv_size; i += 2)
1754 nvs.put_BE<uint16>(MDFN_densb<uint16>((uint8*)nv_ptr + i));
1755 }
1756 else
1757 nvs.write(nv_ptr, nv_size);
1758
1759 nvs.close();
1760 }
1761 }
1762
SaveRTC(void)1763 static MDFN_COLD void SaveRTC(void)
1764 {
1765 FileStream sds(MDFN_MakeFName(MDFNMKF_SAV, 0, "smpc"), FileStream::MODE_WRITE_INPLACE);
1766
1767 SMPC_SaveNV(&sds);
1768
1769 sds.close();
1770 }
1771
LoadRTC(void)1772 static MDFN_COLD void LoadRTC(void)
1773 {
1774 FileStream sds(MDFN_MakeFName(MDFNMKF_SAV, 0, "smpc"), FileStream::MODE_READ);
1775
1776 SMPC_LoadNV(&sds);
1777 }
1778
1779 struct EventsPacker
1780 {
1781 enum : size_t { eventcopy_first = SS_EVENT__SYNFIRST + 1 };
1782 enum : size_t { eventcopy_bound = SS_EVENT__SYNLAST };
1783
1784 bool Restore(const unsigned state_version);
1785 void Save(void);
1786
1787 int32 event_times[eventcopy_bound - eventcopy_first];
1788 uint8 event_order[eventcopy_bound - eventcopy_first];
1789 };
1790
Save(void)1791 INLINE void EventsPacker::Save(void)
1792 {
1793 event_list_entry* evt = events[SS_EVENT__SYNFIRST].next;
1794
1795 for(size_t i = eventcopy_first; i < eventcopy_bound; i++)
1796 {
1797 event_times[i - eventcopy_first] = events[i].event_time;
1798 event_order[i - eventcopy_first] = evt - events;
1799 assert(event_order[i - eventcopy_first] >= eventcopy_first && event_order[i - eventcopy_first] < eventcopy_bound);
1800 evt = evt->next;
1801 }
1802 }
1803
Restore(const unsigned state_version)1804 INLINE bool EventsPacker::Restore(const unsigned state_version)
1805 {
1806 bool used[SS_EVENT__COUNT] = { 0 };
1807 event_list_entry* evt = &events[SS_EVENT__SYNFIRST];
1808 for(size_t i = eventcopy_first; i < eventcopy_bound; i++)
1809 {
1810 int32 et = event_times[i - eventcopy_first];
1811 uint8 eo = event_order[i - eventcopy_first];
1812
1813 if(state_version < 0x00102600 && et >= 0x40000000)
1814 {
1815 et = SS_EVENT_DISABLED_TS;
1816 }
1817
1818 if(eo < eventcopy_first || eo >= eventcopy_bound)
1819 return false;
1820
1821 if(used[eo])
1822 return false;
1823
1824 used[eo] = true;
1825
1826 if(et < events[SS_EVENT__SYNFIRST].event_time)
1827 return false;
1828
1829 events[i].event_time = et;
1830
1831 evt->next = &events[eo];
1832 evt->next->prev = evt;
1833 evt = evt->next;
1834 }
1835 evt->next = &events[SS_EVENT__SYNLAST];
1836 evt->next->prev = evt;
1837
1838 for(size_t i = 0; i < SS_EVENT__COUNT; i++)
1839 {
1840 if(i == SS_EVENT__SYNLAST)
1841 {
1842 if(events[i].next != NULL)
1843 return false;
1844 }
1845 else
1846 {
1847 if(events[i].next->prev != &events[i])
1848 return false;
1849
1850 if(events[i].next->event_time < events[i].event_time)
1851 return false;
1852 }
1853
1854 if(i == SS_EVENT__SYNFIRST)
1855 {
1856 if(events[i].prev != NULL)
1857 return false;
1858 }
1859 else
1860 {
1861 if(events[i].prev->next != &events[i])
1862 return false;
1863
1864 if(events[i].prev->event_time > events[i].event_time)
1865 return false;
1866 }
1867 }
1868
1869 return true;
1870 }
1871
StateAction(StateMem * sm,const unsigned load,const bool data_only)1872 static MDFN_COLD void StateAction(StateMem* sm, const unsigned load, const bool data_only)
1873 {
1874 if(!data_only)
1875 {
1876 sha256_digest sr_dig = BIOS_SHA256;
1877 int cart_type = ActiveCartType;
1878
1879 if(DBG_InSlaveStep())
1880 throw MDFN_Error(0, _("Slave step mode is incompatible with save states."));
1881
1882 SFORMAT SRDStateRegs[] =
1883 {
1884 SFPTR8(sr_dig.data(), sr_dig.size()),
1885 SFVAR(cart_type),
1886 SFEND
1887 };
1888
1889 MDFNSS_StateAction(sm, load, data_only, SRDStateRegs, "BIOS_HASH");
1890
1891 if(load)
1892 {
1893 if(sr_dig != BIOS_SHA256)
1894 throw MDFN_Error(0, _("BIOS hash mismatch(save state created under a different BIOS)!"));
1895 /*
1896 if(load < 0x00102300)
1897 {
1898 SFORMAT DummyStateRegs[] = { SFEND };
1899
1900 if(MDFNSS_StateAction(sm, load, data_only, DummyStateRegs, "CART_BACKUP", true))
1901 cart_type = CART_BACKUP_MEM;
1902 }
1903 */
1904 if(cart_type != ActiveCartType)
1905 throw MDFN_Error(0, _("Cart type mismatch(save state created with a different cart)!"));
1906 }
1907 }
1908 //
1909 //
1910 //
1911 bool RecordedNeedEmuICache = load ? false : NeedEmuICache;
1912 EventsPacker ep;
1913 ep.Save();
1914
1915 SFORMAT StateRegs[] =
1916 {
1917 // cur_clock_div
1918 SFVAR(UpdateInputLastBigTS),
1919
1920 SFVAR(next_event_ts),
1921 SFVARN(ep.event_times, "event_times"),
1922 SFVARN(ep.event_order, "event_order"),
1923
1924 SFVAR(SH7095_mem_timestamp),
1925 SFVAR(SH7095_BusLock),
1926 SFVAR(SH7095_DB),
1927
1928 SFVAR(WorkRAML),
1929 SFVAR(WorkRAMH),
1930 SFVAR(BackupRAM),
1931
1932 SFVAR(RecordedNeedEmuICache),
1933
1934 SFEND
1935 };
1936
1937 CPU[0].StateAction(sm, load, data_only, "SH2-M");
1938 CPU[1].StateAction(sm, load, data_only, "SH2-S");
1939 SCU_StateAction(sm, load, data_only);
1940 SMPC_StateAction(sm, load, data_only);
1941
1942 CDB_StateAction(sm, load, data_only);
1943 VDP1::StateAction(sm, load, data_only);
1944 VDP2::StateAction(sm, load, data_only);
1945
1946 SOUND_StateAction(sm, load, data_only);
1947 CART_StateAction(sm, load, data_only);
1948 //
1949 MDFNSS_StateAction(sm, load, data_only, StateRegs, "MAIN");
1950
1951 if(load)
1952 {
1953 BackupRAM_Dirty = true;
1954
1955 if(!ep.Restore(load))
1956 {
1957 printf("Bad state events data.");
1958 InitEvents();
1959 }
1960
1961 CPU[0].PostStateLoad(load, RecordedNeedEmuICache, NeedEmuICache);
1962 CPU[1].PostStateLoad(load, RecordedNeedEmuICache, NeedEmuICache);
1963 }
1964 }
1965
SetMedia(uint32 drive_idx,uint32 state_idx,uint32 media_idx,uint32 orientation_idx)1966 static MDFN_COLD void SetMedia(uint32 drive_idx, uint32 state_idx, uint32 media_idx, uint32 orientation_idx)
1967 {
1968 const RMD_Layout* rmd = EmulatedSS.RMD;
1969 const RMD_Drive* rd = &rmd->Drives[drive_idx];
1970 const RMD_State* rs = &rd->PossibleStates[state_idx];
1971
1972 //printf("%d %d %d\n", rs->MediaPresent, rs->MediaUsable, rs->MediaCanChange);
1973
1974 if(rs->MediaPresent && rs->MediaUsable)
1975 CDB_SetDisc(false, (*cdifs)[media_idx]);
1976 else
1977 CDB_SetDisc(rs->MediaCanChange, NULL);
1978 }
1979
DoSimpleCommand(int cmd)1980 static void DoSimpleCommand(int cmd)
1981 {
1982 switch(cmd)
1983 {
1984 case MDFN_MSC_POWER:
1985 if(DBG_InSlaveStep())
1986 MDFN_Notify(MDFN_NOTICE_ERROR, _("Slave step mode is incompatible with hard resets."));
1987 else
1988 SS_Reset(true);
1989 break;
1990 // MDFN_MSC_RESET is not handled here; special reset button handling in smpc.cpp.
1991 }
1992 }
1993
1994 static const FileExtensionSpecStruct KnownExtensions[] =
1995 {
1996 { ".ss", 0, gettext_noop("Sega Saturn Debug Cart ROM") },
1997
1998 { NULL, 0, NULL }
1999 };
2000
2001 static const MDFNSetting_EnumList Region_List[] =
2002 {
2003 { "jp", SMPC_AREA_JP, gettext_noop("Japan") },
2004 { "na", SMPC_AREA_NA, gettext_noop("North America") },
2005 { "eu", SMPC_AREA_EU_PAL, gettext_noop("Europe") },
2006 { "kr", SMPC_AREA_KR, gettext_noop("South Korea") },
2007
2008 { "tw", SMPC_AREA_ASIA_NTSC, gettext_noop("Taiwan") }, // Taiwan, Philippines
2009 { "as", SMPC_AREA_ASIA_PAL, gettext_noop("China") }, // China, Middle East
2010
2011 { "br", SMPC_AREA_CSA_NTSC, gettext_noop("Brazil") },
2012 { "la", SMPC_AREA_CSA_PAL, gettext_noop("Latin America") },
2013
2014 { NULL, 0 },
2015 };
2016
2017 static const MDFNSetting_EnumList RTCLang_List[] =
2018 {
2019 { "english", SMPC_RTC_LANG_ENGLISH, gettext_noop("English") },
2020 { "german", SMPC_RTC_LANG_GERMAN, gettext_noop("Deutsch") },
2021 { "french", SMPC_RTC_LANG_FRENCH, gettext_noop("Français") },
2022 { "spanish", SMPC_RTC_LANG_SPANISH, gettext_noop("Español") },
2023 { "italian", SMPC_RTC_LANG_ITALIAN, gettext_noop("Italiano") },
2024 { "japanese", SMPC_RTC_LANG_JAPANESE, gettext_noop("日本語") },
2025
2026 { "deutsch", SMPC_RTC_LANG_GERMAN, NULL },
2027 { "français", SMPC_RTC_LANG_FRENCH, NULL },
2028 { "español", SMPC_RTC_LANG_SPANISH, NULL },
2029 { "italiano", SMPC_RTC_LANG_ITALIAN, NULL },
2030 { "日本語", SMPC_RTC_LANG_JAPANESE, NULL},
2031
2032 { NULL, 0 },
2033 };
2034
2035 #define CART_LIST_BASE \
2036 { "none", CART_NONE, gettext_noop("None") }, \
2037 { "backup", CART_BACKUP_MEM, gettext_noop("Backup Memory(512KiB)") }, \
2038 { "extram1", CART_EXTRAM_1M, gettext_noop("1MiB Extended RAM") }, \
2039 { "extram4", CART_EXTRAM_4M, gettext_noop("4MiB Extended RAM") }, \
2040 { "cs1ram16", CART_CS1RAM_16M, gettext_noop("16MiB RAM mapped in A-bus CS1") }, \
2041 { "ar4mp", CART_AR4MP, NULL }, /* Undocumented, unfinished. gettext_noop("Action Replay 4M Plus") },*/ \
2042 /* { "nlmodem", CART_NLMODEM, gettext_noop("NetLink Modem") }, */
2043
2044 static const MDFNSetting_EnumList Cart_List[] =
2045 {
2046 { "auto", CART__RESERVED, gettext_noop("Automatic") },
2047
2048 CART_LIST_BASE
2049
2050 { NULL, 0 },
2051 };
2052
2053 static const MDFNSetting_EnumList CartAD_List[] =
2054 {
2055 CART_LIST_BASE
2056
2057 { NULL, 0 },
2058 };
2059
2060 #ifdef MDFN_ENABLE_DEV_BUILD
2061 static const MDFNSetting_EnumList DBGMask_List[] =
2062 {
2063 { "0", 0 },
2064 { "none", 0, gettext_noop("None") },
2065
2066 { "all", ~0, gettext_noop("All") },
2067
2068 { "warning", SS_DBG_WARNING, gettext_noop("Warnings") },
2069
2070 { "m68k", SS_DBG_M68K, gettext_noop("M68K") },
2071
2072 { "sh2", SS_DBG_SH2, gettext_noop("SH-2") },
2073 { "sh2_regw", SS_DBG_SH2_REGW, gettext_noop("SH-2 (peripherals) register writes") },
2074 { "sh2_cache", SS_DBG_SH2_CACHE, gettext_noop("SH-2 cache") },
2075 { "sh2_cache_noisy",SS_DBG_SH2_CACHE_NOISY, gettext_noop("SH-2 cache, with assoc purge/addr/data array logging") },
2076 { "sh2_except",SS_DBG_SH2_EXCEPT, gettext_noop("SH-2 exceptions and interrupts") },
2077 { "sh2_dmarace",SS_DBG_SH2_DMARACE, gettext_noop("SH-2 DMA/CPU RW races") },
2078
2079 { "scu", SS_DBG_SCU, gettext_noop("SCU") },
2080 { "scu_regw", SS_DBG_SCU_REGW, gettext_noop("SCU register writes") },
2081 { "scu_int", SS_DBG_SCU_INT, gettext_noop("SCU interrupt") },
2082 { "scu_dsp", SS_DBG_SCU_DSP, gettext_noop("SCU DSP") },
2083
2084 { "smpc", SS_DBG_SMPC, gettext_noop("SMPC") },
2085 { "smpc_regw", SS_DBG_SMPC_REGW, gettext_noop("SMPC register writes") },
2086
2087 { "cdb", SS_DBG_CDB, gettext_noop("CDB") },
2088 { "cdb_regw", SS_DBG_CDB_REGW, gettext_noop("CDB register writes") },
2089
2090 { "vdp1", SS_DBG_VDP1, gettext_noop("VDP1") },
2091 { "vdp1_regw", SS_DBG_VDP1_REGW, gettext_noop("VDP1 register writes") },
2092 { "vdp1_vramw",SS_DBG_VDP1_VRAMW, gettext_noop("VDP1 VRAM writes") },
2093 { "vdp1_fbw", SS_DBG_VDP1_FBW, gettext_noop("VDP1 FB writes") },
2094 { "vdp1_race", SS_DBG_VDP1_RACE, gettext_noop("VDP1 draw/VRAM write races") },
2095
2096 { "vdp2", SS_DBG_VDP2, gettext_noop("VDP2") },
2097 { "vdp2_regw", SS_DBG_VDP2_REGW, gettext_noop("VDP2 register writes") },
2098
2099 { "scsp", SS_DBG_SCSP, gettext_noop("SCSP") },
2100 { "scsp_regw", SS_DBG_SCSP_REGW, gettext_noop("SCSP register writes") },
2101
2102 { "bios", SS_DBG_BIOS, gettext_noop("BIOS") },
2103
2104 { NULL, 0 },
2105 };
2106 #endif
2107
2108 static const MDFNSetting_EnumList CEM_List[] =
2109 {
2110 { "data_cb", CPUCACHE_EMUMODE_DATA_CB, gettext_noop("Data only, with high-level bypass") },
2111 { "data", CPUCACHE_EMUMODE_DATA, gettext_noop("Data only") },
2112 { "full", CPUCACHE_EMUMODE_FULL, gettext_noop("Full") },
2113
2114 { NULL, 0 },
2115 };
2116
2117 static const MDFNSetting_EnumList HH_List[] =
2118 {
2119 { "0", 0 },
2120 { "none", 0, gettext_noop("None") },
2121
2122 { "nosh2dmaline106", HORRIBLEHACK_NOSH2DMALINE106, gettext_noop("nosh2dmaline106") },
2123 { "nosh2dmapenalty", HORRIBLEHACK_NOSH2DMAPENALTY, gettext_noop("nosh2dmapenalty") },
2124 { "vdp1vram5000fix", HORRIBLEHACK_VDP1VRAM5000FIX, gettext_noop("vdp1vram5000fix") },
2125 { "vdp1rwdrawslowdown",HORRIBLEHACK_VDP1RWDRAWSLOWDOWN,gettext_noop("vdp1rwdrawslowdown") },
2126 { "vdp1instant", HORRIBLEHACK_VDP1INSTANT, gettext_noop("vdp1instant") },
2127
2128 { NULL, 0 },
2129 };
2130
2131 static const MDFNSetting SSSettings[] =
2132 {
2133 { "ss.bios_jp", MDFNSF_EMU_STATE | MDFNSF_CAT_PATH, gettext_noop("Path to the Japan ROM BIOS"), NULL, MDFNST_STRING, "sega_101.bin" },
2134 { "ss.bios_na_eu", MDFNSF_EMU_STATE | MDFNSF_CAT_PATH, gettext_noop("Path to the North America and Europe ROM BIOS"), NULL, MDFNST_STRING, "mpr-17933.bin" },
2135
2136 { "ss.scsp.resamp_quality", MDFNSF_NOFLAGS, gettext_noop("SCSP output resampler quality."),
2137 gettext_noop("0 is lowest quality and CPU usage, 10 is highest quality and CPU usage. The resampler that this setting refers to is used for converting from 44.1KHz to the sampling rate of the host audio device Mednafen is using. Changing Mednafen's output rate, via the \"sound.rate\" setting, to \"44100\" may bypass the resampler, which can decrease CPU usage by Mednafen, and can increase or decrease audio quality, depending on various operating system and hardware factors."), MDFNST_UINT, "4", "0", "10" },
2138
2139 { "ss.region_autodetect", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("Attempt to auto-detect region of game."), NULL, MDFNST_BOOL, "1" },
2140 { "ss.region_default", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("Default region to use."), gettext_noop("Used if region autodetection fails or is disabled."), MDFNST_ENUM, "jp", NULL, NULL, NULL, NULL, Region_List },
2141
2142 { "ss.input.mouse_sensitivity", MDFNSF_NOFLAGS, gettext_noop("Emulated mouse sensitivity."), NULL, MDFNST_FLOAT, "0.50", NULL, NULL },
2143 { "ss.input.sport1.multitap", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("Enable multitap on Saturn port 1."), NULL, MDFNST_BOOL, "0", NULL, NULL },
2144 { "ss.input.sport2.multitap", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("Enable multitap on Saturn port 2."), NULL, MDFNST_BOOL, "0", NULL, NULL },
2145
2146 { "ss.input.port1.gun_chairs", MDFNSF_NOFLAGS, gettext_noop("Crosshairs color for lightgun on virtual port 1."), gettext_noop("A value of 0x1000000 disables crosshair drawing."), MDFNST_UINT, "0xFF0000", "0x000000", "0x1000000" },
2147 { "ss.input.port2.gun_chairs", MDFNSF_NOFLAGS, gettext_noop("Crosshairs color for lightgun on virtual port 2."), gettext_noop("A value of 0x1000000 disables crosshair drawing."), MDFNST_UINT, "0x00FF00", "0x000000", "0x1000000" },
2148 { "ss.input.port3.gun_chairs", MDFNSF_NOFLAGS, gettext_noop("Crosshairs color for lightgun on virtual port 3."), gettext_noop("A value of 0x1000000 disables crosshair drawing."), MDFNST_UINT, "0xFF00FF", "0x000000", "0x1000000" },
2149 { "ss.input.port4.gun_chairs", MDFNSF_NOFLAGS, gettext_noop("Crosshairs color for lightgun on virtual port 4."), gettext_noop("A value of 0x1000000 disables crosshair drawing."), MDFNST_UINT, "0xFF8000", "0x000000", "0x1000000" },
2150 { "ss.input.port5.gun_chairs", MDFNSF_NOFLAGS, gettext_noop("Crosshairs color for lightgun on virtual port 5."), gettext_noop("A value of 0x1000000 disables crosshair drawing."), MDFNST_UINT, "0xFFFF00", "0x000000", "0x1000000" },
2151 { "ss.input.port6.gun_chairs", MDFNSF_NOFLAGS, gettext_noop("Crosshairs color for lightgun on virtual port 6."), gettext_noop("A value of 0x1000000 disables crosshair drawing."), MDFNST_UINT, "0x00FFFF", "0x000000", "0x1000000" },
2152 { "ss.input.port7.gun_chairs", MDFNSF_NOFLAGS, gettext_noop("Crosshairs color for lightgun on virtual port 7."), gettext_noop("A value of 0x1000000 disables crosshair drawing."), MDFNST_UINT, "0x0080FF", "0x000000", "0x1000000" },
2153 { "ss.input.port8.gun_chairs", MDFNSF_NOFLAGS, gettext_noop("Crosshairs color for lightgun on virtual port 8."), gettext_noop("A value of 0x1000000 disables crosshair drawing."), MDFNST_UINT, "0x8000FF", "0x000000", "0x1000000" },
2154 { "ss.input.port9.gun_chairs", MDFNSF_NOFLAGS, gettext_noop("Crosshairs color for lightgun on virtual port 9."), gettext_noop("A value of 0x1000000 disables crosshair drawing."), MDFNST_UINT, "0xFF80FF", "0x000000", "0x1000000" },
2155 { "ss.input.port10.gun_chairs", MDFNSF_NOFLAGS, gettext_noop("Crosshairs color for lightgun on virtual port 10."), gettext_noop("A value of 0x1000000 disables crosshair drawing."), MDFNST_UINT, "0x00FF80", "0x000000", "0x1000000" },
2156 { "ss.input.port11.gun_chairs", MDFNSF_NOFLAGS, gettext_noop("Crosshairs color for lightgun on virtual port 11."), gettext_noop("A value of 0x1000000 disables crosshair drawing."), MDFNST_UINT, "0x8080FF", "0x000000", "0x1000000" },
2157 { "ss.input.port12.gun_chairs", MDFNSF_NOFLAGS, gettext_noop("Crosshairs color for lightgun on virtual port 12."), gettext_noop("A value of 0x1000000 disables crosshair drawing."), MDFNST_UINT, "0xFF8080", "0x000000", "0x1000000" },
2158
2159 { "ss.smpc.autortc", MDFNSF_NOFLAGS, gettext_noop("Automatically set RTC on game load."), gettext_noop("Automatically set the SMPC's emulated Real-Time Clock to the host system's current time and date upon game load."), MDFNST_BOOL, "1" },
2160 { "ss.smpc.autortc.lang", MDFNSF_NOFLAGS, gettext_noop("BIOS language."), gettext_noop("Also affects language used in some games(e.g. the European release of \"Panzer Dragoon\")."), MDFNST_ENUM, "english", NULL, NULL, NULL, NULL, RTCLang_List },
2161
2162 { "ss.cart", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("Expansion cart."), NULL, MDFNST_ENUM, "auto", NULL, NULL, NULL, NULL, Cart_List },
2163 { "ss.cart.auto_default", MDFNSF_EMU_STATE | MDFNSF_UNTRUSTED_SAFE, gettext_noop("Default expansion cart when autodetection fails."), gettext_noop("Expansion cart to emulate when \"ss.cart\" is set to \"auto\", but the game wasn't found in the internal database for carts."), MDFNST_ENUM, "backup", NULL, NULL, NULL, NULL, CartAD_List },
2164
2165 { "ss.cart.kof95_path", MDFNSF_EMU_STATE | MDFNSF_CAT_PATH, gettext_noop("Path to KoF 95 ROM image."), NULL, MDFNST_STRING, "mpr-18811-mx.ic1" },
2166 { "ss.cart.ultraman_path", MDFNSF_EMU_STATE | MDFNSF_CAT_PATH, gettext_noop("Path to Ultraman ROM image."), NULL, MDFNST_STRING, "mpr-19367-mx.ic1" },
2167 { "ss.cart.satar4mp_path", MDFNSF_EMU_STATE | MDFNSF_CAT_PATH | MDFNSF_SUPPRESS_DOC | MDFNSF_NONPERSISTENT, gettext_noop("Path to Action Replay 4M Plus firmware image."), NULL, MDFNST_STRING, "satar4mp.bin" },
2168 // { "ss.cart.modem_port", MDFNSF_NOFLAGS, gettext_noop("TCP/IP port to use for modem emulation."), gettext_noop("A value of \"0\" disables network access."), MDFNST_UINT, "4920", "0", "65535" },
2169
2170 { "ss.bios_sanity", MDFNSF_NOFLAGS, gettext_noop("Enable BIOS ROM image sanity checks."), NULL, MDFNST_BOOL, "1" },
2171
2172 { "ss.cd_sanity", MDFNSF_NOFLAGS, gettext_noop("Enable CD (image) sanity checks."), NULL, MDFNST_BOOL, "1" },
2173
2174 { "ss.slstart", MDFNSF_NOFLAGS, gettext_noop("First displayed scanline in NTSC mode."), NULL, MDFNST_INT, "0", "0", "239" },
2175 { "ss.slend", MDFNSF_NOFLAGS, gettext_noop("Last displayed scanline in NTSC mode."), NULL, MDFNST_INT, "239", "0", "239" },
2176
2177 { "ss.h_overscan", MDFNSF_NOFLAGS, gettext_noop("Show horizontal overscan area."), NULL, MDFNST_BOOL, "1" },
2178
2179 { "ss.h_blend", MDFNSF_NOFLAGS, gettext_noop("Enable horizontal blend(blur) filter."), gettext_noop("Intended for use in combination with the \"goat\" OpenGL shader, or with bilinear interpolation or linear interpolation on the X axis enabled. Has a more noticeable effect with the Saturn's higher horizontal resolution modes(640/704)."), MDFNST_BOOL, "0" },
2180
2181 { "ss.correct_aspect", MDFNSF_NOFLAGS, gettext_noop("Correct aspect ratio."), gettext_noop("Disabling aspect ratio correction with this setting should be considered a hack.\n\nIf disabling it to allow for sharper pixels by also separately disabling interpolation(though using Mednafen's \"autoipsharper\" OpenGL shader is usually a better option), remember to use scale factors that are multiples of 2, or else games that use high-resolution and interlaced modes will have distorted pixels.\n\nDisabling aspect ratio correction with this setting will allow for the QuickTime movie recording feature to produce much smaller files using much less CPU time."), MDFNST_BOOL, "1" },
2182
2183 { "ss.slstartp", MDFNSF_NOFLAGS, gettext_noop("First displayed scanline in PAL mode."), NULL, MDFNST_INT, "0", "-16", "271" },
2184 { "ss.slendp", MDFNSF_NOFLAGS, gettext_noop("Last displayed scanline in PAL mode."), NULL, MDFNST_INT, "255", "-16", "271" },
2185
2186 { "ss.affinity.vdp2", MDFNSF_NOFLAGS, gettext_noop("VDP2 rendering thread CPU affinity mask."), gettext_noop("Set to 0 to disable changing affinity."), MDFNST_UINT, "0", "0x0000000000000000", "0xFFFFFFFFFFFFFFFF" },
2187
2188 #ifdef MDFN_ENABLE_DEV_BUILD
2189 { "ss.dbg_mask", MDFNSF_SUPPRESS_DOC, gettext_noop("Debug printf mask."), NULL, MDFNST_MULTI_ENUM, "none", NULL, NULL, NULL, NULL, DBGMask_List },
2190 #endif
2191
2192 { "ss.dbg_exe_cdpath", MDFNSF_SUPPRESS_DOC | MDFNSF_CAT_PATH, gettext_noop("CD image to use with bootable cart ROM image loading."), NULL, MDFNST_STRING, "" },
2193 { "ss.dbg_exe_cem", MDFNSF_SUPPRESS_DOC | MDFNSF_NONPERSISTENT, gettext_noop("Cache emulation mode to use with bootable cart ROM image loading."), NULL, MDFNST_ENUM, "data", NULL, NULL, NULL, NULL, CEM_List },
2194 { "ss.dbg_exe_hh", MDFNSF_SUPPRESS_DOC | MDFNSF_NONPERSISTENT, gettext_noop("Horrible hacks to use with bootable cart ROM image loading."), NULL, MDFNST_MULTI_ENUM, "none", NULL, NULL, NULL, NULL, HH_List },
2195
2196 { NULL },
2197 };
2198
2199 static const CheatInfoStruct CheatInfo =
2200 {
2201 NULL,
2202 NULL,
2203
2204 CheatMemRead,
2205 CheatMemWrite,
2206
2207 CheatFormatInfo_Empty,
2208
2209 true
2210 };
2211
2212 }
2213
2214 using namespace MDFN_IEN_SS;
2215
2216 MDFNGI EmulatedSS =
2217 {
2218 "ss",
2219 "Sega Saturn",
2220 KnownExtensions,
2221 MODPRIO_INTERNAL_HIGH,
2222 #ifdef WANT_DEBUGGER
2223 &DBGInfo,
2224 #else
2225 NULL,
2226 #endif
2227 SMPC_PortInfo,
2228 DB_GetInternalDB,
2229 Load,
2230 TestMagic,
2231 LoadCD,
2232 TestMagicCD,
2233 CloseGame,
2234
2235 VDP2::SetLayerEnableMask,
2236 "NBG0\0NBG1\0NBG2\0NBG3\0RBG0\0RBG1\0Sprite\0",
2237
2238 NULL,
2239 NULL,
2240
2241 NULL,
2242 0,
2243
2244 CheatInfo,
2245
2246 false,
2247 StateAction,
2248 Emulate,
2249 SMPC_TransformInput,
2250 SMPC_SetInput,
2251 SetMedia,
2252 DoSimpleCommand,
2253 NULL,
2254 SSSettings,
2255 0,
2256 0,
2257
2258 true, // Multires possible?
2259
2260 //
2261 // Note: Following video settings will be overwritten during game load.
2262 //
2263 320, // lcm_width
2264 240, // lcm_height
2265 NULL, // Dummy
2266
2267 302, // Nominal width
2268 240, // Nominal height
2269
2270 0, // Framebuffer width
2271 0, // Framebuffer height
2272 //
2273 //
2274 //
2275
2276 2, // Number of output sound channels
2277 };
2278
2279