1 /* Mednafen - Multi-system Emulator
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 */
17
18 #include "mednafen.h"
19
20 #include <mednafen/cdrom/CDUtility.h>
21 #include <mednafen/cdrom/CDInterface.h>
22
23 #include <mednafen/string/string.h>
24 #include <mednafen/string/escape.h>
25
26 #include <mednafen/hash/md5.h>
27 #include <mednafen/MemoryStream.h>
28 #include <mednafen/Time.h>
29
30 #include <mednafen/sound/Fir_Resampler.h>
31 #include <mednafen/sound/WAVRecord.h>
32
33 #include <mednafen/NativeVFS.h>
34
35 #include <minilzo/minilzo.h>
36
37 #include <trio/trio.h>
38
39 #include "netplay.h"
40 #include "netplay-driver.h"
41 #include "general.h"
42
43 #include "state.h"
44 #include "movie.h"
45 #include "state_rewind.h"
46 #include "video.h"
47 #include "video/Deinterlacer.h"
48 #include "file.h"
49 #include "mempatcher.h"
50 #include "tests.h"
51 #include "video/tblur.h"
52 #include "qtrecord.h"
53
54 namespace Mednafen
55 {
56
57 NativeVFS NVFS;
58
59 static void SettingChanged(const char* name);
60
61 static const char *CSD_forcemono = gettext_noop("Force monophonic sound output.");
62 static const char *CSD_enable = gettext_noop("Enable (automatic) usage of this module.");
63 static const char *CSD_tblur = gettext_noop("Enable video temporal blur(50/50 previous/current frame by default).");
64 static const char *CSD_tblur_accum = gettext_noop("Accumulate color data rather than discarding it.");
65 static const char *CSD_tblur_accum_amount = gettext_noop("Blur amount in accumulation mode, specified in percentage of accumulation buffer to mix with the current frame.");
66
67 static const MDFNSetting_EnumList VCodec_List[] =
68 {
69 { "raw", (int)QTRecord::VCODEC_RAW, "Raw",
70 gettext_noop("A fast codec, computationally, but will cause enormous file size and may exceed your storage medium's sustained write rate.") },
71
72 { "cscd", (int)QTRecord::VCODEC_CSCD, "CamStudio Screen Codec",
73 gettext_noop("A good balance between performance and compression ratio.") },
74
75 { "png", (int)QTRecord::VCODEC_PNG, "PNG",
76 gettext_noop("Has a better compression ratio than \"cscd\", but is much more CPU intensive. Use for compatibility with official QuickTime in cases where you have insufficient disk space for \"raw\".") },
77
78 { NULL, 0 },
79 };
80
81 static const MDFNSetting_EnumList Deinterlacer_List[] =
82 {
83 { "weave", Deinterlacer::DEINT_WEAVE, gettext_noop("Good for low-motion video; can be used in conjunction with negative <system>.scanlines setting values.") },
84 { "bob", Deinterlacer::DEINT_BOB, gettext_noop("Good for causing a headache. All glory to Bob.") },
85 { "bob_offset", Deinterlacer::DEINT_BOB_OFFSET, gettext_noop("Good for high-motion video, but is a bit flickery; reduces the subjective vertical resolution.") },
86
87 { "blend", Deinterlacer::DEINT_BLEND, gettext_noop("Blend fields together; reduces vertical and temporal resolution.") },
88 { "blend_rg", Deinterlacer::DEINT_BLEND_RG, gettext_noop("Like the \"blend\" deinterlacer, but the blending is done in a manner that respects gamma, reducing unwanted brightness changes, at the cost of increased CPU usage.") },
89
90 { NULL, 0 },
91 };
92
93 static const char* const fname_extra = gettext_noop("See fname_format.txt for more information. Edit at your own risk.");
94
95 static const MDFNSetting MednafenSettings[] =
96 {
97 { "netplay.password", MDFNSF_NOFLAGS, gettext_noop("Server password."), gettext_noop("Password to connect to the netplay server."), MDFNST_STRING, "" },
98 { "netplay.localplayers", MDFNSF_NOFLAGS, gettext_noop("Local player count."), gettext_noop("Number of local players for network play. This number is advisory to the server, and the server may assign fewer players if the number of players requested is higher than the number of controllers currently available."), MDFNST_UINT, "1", "0", "16" },
99 { "netplay.nick", MDFNSF_NOFLAGS, gettext_noop("Nickname."), gettext_noop("Nickname to use for network play chat."), MDFNST_STRING, "" },
100 { "netplay.gamekey", MDFNSF_NOFLAGS, gettext_noop("Key to hash with the MD5 hash of the game."), NULL, MDFNST_STRING, "" },
101
102 { "srwframes", MDFNSF_NOFLAGS, gettext_noop("Number of frames to keep states for when state rewinding is enabled."),
103 gettext_noop("WARNING: Setting this to a large value may cause excessive RAM usage in some circumstances, such as with games that stream large volumes of data off of CDs."), MDFNST_UINT, "600", "10", "99999" },
104
105 { "cd.image_memcache", MDFNSF_NOFLAGS, gettext_noop("Cache entire CD images in memory."), gettext_noop("Reads the entire CD image(s) into memory at startup(which will cause a small delay). Can help obviate emulation hiccups due to emulated CD access. May cause more harm than good on low memory systems, systems with swap enabled, and/or when the disc images in question are on a fast SSD."), MDFNST_BOOL, "0" },
106
107 { "filesys.untrusted_fip_check", MDFNSF_NOFLAGS, gettext_noop("Enable untrusted file-inclusion path security check."),
108 gettext_noop("When this setting is set to \"1\", the default, paths to files referenced from files like CUE sheets and PSF rips are checked for certain characters that can be used in directory traversal, and if found, loading is aborted. Set it to \"0\" if you want to allow constructs like absolute paths in CUE sheets, but only if you understand the security implications of doing so(see \"Security Issues\" section in the documentation)."), MDFNST_BOOL, "1" },
109
110 { "filesys.path_snap", MDFNSF_CAT_PATH, gettext_noop("Path to directory for screen snapshots."), NULL, MDFNST_STRING, "snaps" },
111 { "filesys.path_sav", MDFNSF_CAT_PATH, gettext_noop("Path to directory for save games and nonvolatile memory."), gettext_noop("WARNING: Do not set this path to a directory that contains Famicom Disk System disk images, or you will corrupt them when you load an FDS game and exit Mednafen."), MDFNST_STRING, "sav" },
112 { "filesys.path_savbackup", MDFNSF_CAT_PATH, gettext_noop("Path to directory for backups of save games and nonvolatile memory."), NULL, MDFNST_STRING, "b" },
113 { "filesys.path_state", MDFNSF_CAT_PATH, gettext_noop("Path to directory for save states."), NULL, MDFNST_STRING, "mcs" },
114 { "filesys.path_movie", MDFNSF_CAT_PATH, gettext_noop("Path to directory for movies."), NULL, MDFNST_STRING, "mcm" },
115 { "filesys.path_cheat", MDFNSF_CAT_PATH, gettext_noop("Path to directory for cheats."), NULL, MDFNST_STRING, "cheats" },
116 { "filesys.path_palette", MDFNSF_CAT_PATH, gettext_noop("Path to directory for custom palettes."), NULL, MDFNST_STRING, "palettes" },
117 { "filesys.path_pgconfig", MDFNSF_CAT_PATH, gettext_noop("Path to directory for per-game configuration override files."), NULL, MDFNST_STRING, "pgconfig" },
118 { "filesys.path_firmware", MDFNSF_CAT_PATH, gettext_noop("Path to directory for firmware."), NULL, MDFNST_STRING, "firmware" },
119
120 { "filesys.fname_movie", MDFNSF_CAT_PATH, gettext_noop("Format string for movie filename."), fname_extra, MDFNST_STRING, "%f.%M%p.%x" },
121 { "filesys.fname_state", MDFNSF_CAT_PATH, gettext_noop("Format string for state filename."), fname_extra, MDFNST_STRING, "%f.%M%X" /*"%F.%M%p.%x"*/ },
122 { "filesys.fname_sav", MDFNSF_CAT_PATH, gettext_noop("Format string for save games filename."), gettext_noop("WARNING: %x should always be included, otherwise you run the risk of overwriting save data for games that create multiple save data files.\n\nSee fname_format.txt for more information. Edit at your own risk."), MDFNST_STRING, "%f.%M%x" },
123 { "filesys.fname_savbackup", MDFNSF_CAT_PATH, gettext_noop("Format string for save game backups filename."), gettext_noop("WARNING: %x and %p should always be included.\n\nSee fname_format.txt for more information. Edit at your own risk."), MDFNST_STRING, "%f.%m%z%p.%x" },
124 { "filesys.fname_snap", MDFNSF_CAT_PATH, gettext_noop("Format string for screen snapshot filenames."), gettext_noop("WARNING: %x or %p should always be included, otherwise there will be a conflict between the numeric counter text file and the image data file.\n\nSee fname_format.txt for more information. Edit at your own risk."), MDFNST_STRING, "%f-%p.%x" },
125
126 { "filesys.state_comp_level", MDFNSF_NOFLAGS, gettext_noop("Save state file compression level."), gettext_noop("gzip/deflate compression level for save states saved to files. -1 will disable gzip compression and wrapping entirely."), MDFNST_INT, "6", "-1", "9" },
127
128
129 { "qtrecord.w_double_threshold", MDFNSF_NOFLAGS, gettext_noop("Double the raw image's width if it's below this threshold."), NULL, MDFNST_UINT, "384", "0", "1073741824" },
130 { "qtrecord.h_double_threshold", MDFNSF_NOFLAGS, gettext_noop("Double the raw image's height if it's below this threshold."), NULL, MDFNST_UINT, "256", "0", "1073741824" },
131
132 { "qtrecord.vcodec", MDFNSF_NOFLAGS, gettext_noop("Video codec to use."), NULL, MDFNST_ENUM, "cscd", NULL, NULL, NULL, NULL, VCodec_List },
133
134 { "video.deinterlacer", MDFNSF_CAT_VIDEO, gettext_noop("Deinterlacer to use for interlaced video."), NULL, MDFNST_ENUM, "weave", NULL, NULL, NULL, SettingChanged, Deinterlacer_List },
135
136 { "affinity.cd", MDFNSF_NOFLAGS, gettext_noop("CD read threads CPU affinity mask."), gettext_noop("Set to 0 to disable changing affinity."), MDFNST_UINT, "0", "0x0000000000000000", "0xFFFFFFFFFFFFFFFF" },
137
138 { NULL }
139 };
140
141 static const MDFNSetting RenamedSettings[] =
142 {
143 { "path_snap", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "filesys.path_snap" },
144 { "path_sav", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "filesys.path_sav" },
145 { "path_state", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "filesys.path_state" },
146 { "path_movie", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "filesys.path_movie" },
147 { "path_cheat", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "filesys.path_cheat" },
148 { "path_palette", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "filesys.path_palette" },
149 { "path_firmware", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "filesys.path_firmware" },
150
151 { "sounddriver", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "sound.driver" },
152 { "sounddevice", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "sound.device" },
153 { "soundrate", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "sound.rate" },
154 { "soundvol", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "sound.volume" },
155 { "soundbufsize", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "sound.buffer_time" },
156
157 { "nethost", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "netplay.host" },
158 { "netport", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "netplay.port" },
159 { "netpassword", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "netplay.password"},
160 { "netlocalplayers", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "netplay.localplayers" },
161 { "netnick", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "netplay.nick" },
162 { "netgamekey", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "netplay.gamekey" },
163 { "netsmallfont", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "netplay.smallfont" },
164
165 { "frameskip", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "video.frameskip" },
166 { "vdriver", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "video.driver" },
167 { "glvsync", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "video.glvsync" },
168 { "fs", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "video.fs" },
169
170 { "autofirefreq", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "input.autofirefreq" },
171 { "analogthreshold", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "input.joystick.axis_threshold" },
172 { "ckdelay", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "input.ckdelay" },
173
174
175 { "psx.input.port1.multitap", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "psx.input.pport1.multitap" },
176 { "psx.input.port2.multitap", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "psx.input.pport2.multitap" },
177
178 { "snes_faust.spexf", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "snes_faust.spex" },
179
180 { "netplay.smallfont", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "netplay.console.font" },
181
182 { "cdplay.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "cdplay.shader" },
183 { "demo.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "demo.shader" },
184 { "gb.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "gb.shader" },
185 { "gba.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "gba.shader" },
186 { "gg.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "gg.shader" },
187 { "lynx.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "lynx.shader" },
188 { "md.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "md.shader" },
189 { "nes.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "nes.shader" },
190 { "ngp.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "ngp.shader" },
191 { "pce.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "pce.shader" },
192 { "pce_fast.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "pce_fast.shader" },
193 { "pcfx.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "pcfx.shader" },
194 { "player.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "player.shader" },
195 { "psx.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "psx.shader" },
196 { "sms.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "sms.shader" },
197 { "snes.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "snes.shader" },
198 { "snes_faust.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "snes_faust.shader" },
199 { "ss.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "ss.shader" },
200 { "ssfplay.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "ssfplay.shader" },
201 { "vb.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "vb.shader" },
202 { "wswan.pixshader", MDFNSF_NOFLAGS, NULL, NULL, MDFNST_ALIAS , "wswan.shader" },
203
204 { NULL }
205 };
206
207 static uint8* CustomPalette = NULL;
208 static uint32 CustomPaletteNumEntries = 0;
209
210 static uint32 PortDevice[16];
211 static uint8* PortData[16];
212 static uint32 PortDataLen[16];
213
214 MDFNGI *MDFNGameInfo = NULL;
215
216 static QTRecord *qtrecorder = NULL;
217 static WAVRecord *wavrecorder = NULL;
218 static Fir_Resampler<16> ff_resampler;
219 static double LastSoundMultiplier;
220 static double last_sound_rate;
221 static MDFN_PixelFormat last_pixel_format;
222 static bool PrevInterlaced;
223 static std::unique_ptr<Deinterlacer> deint;
224
225 static bool FFDiscard = false; // TODO: Setting to discard sound samples instead of increasing pitch
226
227 static std::vector<CDInterface *> CDInterfaces; // FIXME: Cleanup on error out.
228
229 struct DriveMediaStatus
230 {
231 uint32 state_idx = 0;
232 uint32 media_idx = 0;
233 uint32 orientation_idx = 0;
234 };
235
236 static std::vector<DriveMediaStatus> DMStatus, DMStatusSaveStateTemp;
237 static std::vector<uint32> DMSNoMedia;
238 static bool ValidateDMS(const std::vector<DriveMediaStatus>& dms);
239
SettingChanged(const char * name)240 static void SettingChanged(const char* name)
241 {
242 if(!strcmp(name, "video.deinterlacer"))
243 {
244 deint.reset(nullptr);
245 deint.reset(Deinterlacer::Create(MDFN_GetSettingUI(name)));
246 }
247 }
248
MDFNI_StartWAVRecord(const char * path,double SoundRate)249 bool MDFNI_StartWAVRecord(const char *path, double SoundRate)
250 {
251 try
252 {
253 wavrecorder = new WAVRecord(path, SoundRate, MDFNGameInfo->soundchan);
254 }
255 catch(std::exception &e)
256 {
257 MDFND_OutputNotice(MDFN_NOTICE_ERROR, e.what());
258 return(false);
259 }
260
261 return(true);
262 }
263
MDFNI_StartAVRecord(const char * path,double SoundRate)264 bool MDFNI_StartAVRecord(const char *path, double SoundRate)
265 {
266 try
267 {
268 QTRecord::VideoSpec spec;
269
270 memset(&spec, 0, sizeof(spec));
271
272 spec.SoundRate = SoundRate;
273 spec.SoundChan = MDFNGameInfo->soundchan;
274 spec.VideoWidth = MDFNGameInfo->lcm_width;
275 spec.VideoHeight = MDFNGameInfo->lcm_height;
276 spec.VideoCodec = MDFN_GetSettingI("qtrecord.vcodec");
277 spec.MasterClock = MDFNGameInfo->MasterClock;
278
279 if(spec.VideoWidth < MDFN_GetSettingUI("qtrecord.w_double_threshold"))
280 spec.VideoWidth *= 2;
281
282 if(spec.VideoHeight < MDFN_GetSettingUI("qtrecord.h_double_threshold"))
283 spec.VideoHeight *= 2;
284
285
286 spec.AspectXAdjust = ((double)MDFNGameInfo->nominal_width * 2) / spec.VideoWidth;
287 spec.AspectYAdjust = ((double)MDFNGameInfo->nominal_height * 2) / spec.VideoHeight;
288
289 MDFN_printf("\n");
290 MDFN_printf(_("Starting QuickTime recording to file \"%s\":\n"), path);
291 MDFN_indent(1);
292 MDFN_printf(_("Video width: %u\n"), spec.VideoWidth);
293 MDFN_printf(_("Video height: %u\n"), spec.VideoHeight);
294 MDFN_printf(_("Video codec: %s\n"), MDFN_GetSettingS("qtrecord.vcodec").c_str());
295
296 if(spec.SoundRate && spec.SoundChan)
297 {
298 MDFN_printf(_("Sound rate: %u\n"), std::min<uint32>(spec.SoundRate, 64000));
299 MDFN_printf(_("Sound channels: %u\n"), spec.SoundChan);
300 }
301 else
302 MDFN_printf(_("Sound: Disabled\n"));
303
304 MDFN_indent(-1);
305 MDFN_printf("\n");
306
307 qtrecorder = new QTRecord(path, spec);
308 }
309 catch(std::exception &e)
310 {
311 MDFND_OutputNotice(MDFN_NOTICE_ERROR, e.what());
312 return(false);
313 }
314 return(true);
315 }
316
MDFNI_StopAVRecord(void)317 void MDFNI_StopAVRecord(void)
318 {
319 if(qtrecorder)
320 {
321 delete qtrecorder;
322 qtrecorder = NULL;
323 }
324 }
325
MDFNI_StopWAVRecord(void)326 void MDFNI_StopWAVRecord(void)
327 {
328 if(wavrecorder)
329 {
330 delete wavrecorder;
331 wavrecorder = NULL;
332 }
333 }
334
MDFNI_CloseGame(void)335 void MDFNI_CloseGame(void)
336 {
337 if(MDFNGameInfo)
338 {
339 MDFNI_NetplayDisconnect();
340
341 MDFNSRW_End();
342 MDFNMOV_Stop();
343
344 if(MDFNGameInfo->GameType != GMT_PLAYER)
345 MDFN_FlushGameCheats(0);
346
347 MDFNGameInfo->CloseGame();
348
349 //
350 //
351 //
352 MDFNGameInfo->name.clear();
353
354 if(MDFNGameInfo->RMD)
355 {
356 delete MDFNGameInfo->RMD;
357 MDFNGameInfo->RMD = NULL;
358 }
359
360 MDFNMP_Kill();
361
362
363 //
364 //
365 memset(MDFNGameInfo->MD5, 0, sizeof(MDFNGameInfo->MD5));
366 MDFNGameInfo = NULL;
367
368 for(unsigned i = 0; i < CDInterfaces.size(); i++)
369 delete CDInterfaces[i];
370 CDInterfaces.clear();
371 }
372 TBlur_Kill();
373
374 #ifdef WANT_DEBUGGER
375 MDFNDBG_Kill();
376 #endif
377
378 for(unsigned x = 0; x < 16; x++)
379 {
380 if(PortData[x])
381 {
382 free(PortData[x]);
383 PortData[x] = NULL;
384 }
385
386 PortDevice[x] = ~0U;
387 PortDataLen[x] = 0;
388 }
389
390 if(CustomPalette != NULL)
391 {
392 delete[] CustomPalette;
393 CustomPalette = NULL;
394 }
395 CustomPaletteNumEntries = 0;
396
397 MDFN_ClearAllOverrideSettings();
398 }
399
400 }
401
402 #ifdef WANT_APPLE2_EMU
403 extern Mednafen::MDFNGI EmulatedApple2;
404 #endif
405
406 #ifdef WANT_NES_EMU
407 extern Mednafen::MDFNGI EmulatedNES;
408 #endif
409
410 #ifdef WANT_NES_NEW_EMU
411 extern Mednafen::MDFNGI EmulatedNES_New;
412 #endif
413
414 #ifdef WANT_SNES_EMU
415 extern Mednafen::MDFNGI EmulatedSNES;
416 #endif
417
418 #ifdef WANT_SNES_FAUST_EMU
419 extern Mednafen::MDFNGI EmulatedSNES_Faust;
420 #endif
421
422 #ifdef WANT_GBA_EMU
423 extern Mednafen::MDFNGI EmulatedGBA;
424 #endif
425
426 #ifdef WANT_GB_EMU
427 extern Mednafen::MDFNGI EmulatedGB;
428 #endif
429
430 #ifdef WANT_LYNX_EMU
431 extern Mednafen::MDFNGI EmulatedLynx;
432 #endif
433
434 #ifdef WANT_MD_EMU
435 extern Mednafen::MDFNGI EmulatedMD;
436 #endif
437
438 #ifdef WANT_NGP_EMU
439 extern Mednafen::MDFNGI EmulatedNGP;
440 #endif
441
442 #ifdef WANT_PCE_EMU
443 extern Mednafen::MDFNGI EmulatedPCE;
444 #endif
445
446 #ifdef WANT_PCE_FAST_EMU
447 extern Mednafen::MDFNGI EmulatedPCE_Fast;
448 #endif
449
450 #ifdef WANT_PCFX_EMU
451 extern Mednafen::MDFNGI EmulatedPCFX;
452 #endif
453
454 #ifdef WANT_PSX_EMU
455 extern Mednafen::MDFNGI EmulatedPSX;
456 #endif
457
458 #ifdef WANT_SS_EMU
459 extern Mednafen::MDFNGI EmulatedSS;
460 #endif
461
462 #ifdef WANT_SSFPLAY_EMU
463 extern Mednafen::MDFNGI EmulatedSSFPlay;
464 #endif
465
466 #ifdef WANT_VB_EMU
467 extern Mednafen::MDFNGI EmulatedVB;
468 #endif
469
470 #ifdef WANT_WSWAN_EMU
471 extern Mednafen::MDFNGI EmulatedWSwan;
472 #endif
473
474 #ifdef WANT_SMS_EMU
475 extern Mednafen::MDFNGI EmulatedSMS, EmulatedGG;
476 #endif
477
478 extern Mednafen::MDFNGI EmulatedCDPlay;
479 extern Mednafen::MDFNGI EmulatedDEMO;
480
481 namespace Mednafen
482 {
483 std::vector<MDFNGI *> MDFNSystems;
484 static std::list<MDFNGI *> MDFNSystemsPrio;
485
MDFNSystemsPrio_CompareFunc(const MDFNGI * first,const MDFNGI * second)486 bool MDFNSystemsPrio_CompareFunc(const MDFNGI* first, const MDFNGI* second)
487 {
488 if(first->ModulePriority > second->ModulePriority)
489 return true;
490
491 return false;
492 }
493
AddSystem(MDFNGI * system)494 static void AddSystem(MDFNGI *system)
495 {
496 MDFNSystems.push_back(system);
497 }
498
MDFNI_DumpModulesDef(const char * fn)499 void MDFNI_DumpModulesDef(const char *fn)
500 {
501 FileStream fp(fn, FileStream::MODE_WRITE);
502
503 fp.print_format("%s\n", MEDNAFEN_VERSION);
504
505 for(unsigned int i = 0; i < MDFNSystems.size(); i++)
506 {
507 fp.print_format("%s\n", MDFNSystems[i]->shortname);
508 fp.print_format("%s\n", MDFNSystems[i]->fullname);
509 fp.print_format("%d\n", MDFNSystems[i]->nominal_width);
510 fp.print_format("%d\n", MDFNSystems[i]->nominal_height);
511
512 size_t cpcount = 0;
513
514 if(MDFNSystems[i]->CPInfo)
515 for(auto cpi = MDFNSystems[i]->CPInfo; cpi->description || cpi->name_override; cpi++)
516 cpcount++;
517
518 fp.print_format("%zu\n", cpcount);
519
520 if(MDFNSystems[i]->CPInfo)
521 {
522 for(auto cpi = MDFNSystems[i]->CPInfo; cpi->description || cpi->name_override; cpi++)
523 {
524 fp.print_format("%s.pal\n", cpi->name_override ? cpi->name_override : MDFNSystems[i]->shortname);
525 fp.print_format("%s\n", cpi->description);
526 for(unsigned vec : cpi->valid_entry_count)
527 {
528 if(!vec)
529 break;
530 fp.print_format("%u ", vec);
531 }
532 fp.print_format("\n");
533 }
534 }
535
536 std::vector<GameDB_Database> gamedb;
537
538 if(MDFNSystems[i]->GetInternalDB)
539 MDFNSystems[i]->GetInternalDB(&gamedb);
540
541 fp.print_format("%zu\n", gamedb.size());
542
543 for(const GameDB_Database& db : gamedb)
544 {
545 fp.print_format("%s\n", MDFN_strescape(db.ShortName).c_str());
546 fp.print_format("%s\n", MDFN_strescape(db.FullName).c_str());
547 fp.print_format("%s\n", MDFN_strescape(db.Description).c_str());
548
549 fp.print_format("%zu\n", db.Entries.size());
550 for(const GameDB_Entry& gdbe : db.Entries)
551 {
552 fp.print_format("%s\n", MDFN_strescape(gdbe.Name).c_str());
553 fp.print_format("%s\n", MDFN_strescape(gdbe.GameID).c_str());
554 fp.print_format("%u\n", gdbe.GameIDIsHash);
555 fp.print_format("%s\n", MDFN_strescape(gdbe.Setting).c_str());
556 fp.print_format("%s\n", MDFN_strescape(gdbe.Purpose).c_str());
557 }
558 }
559 }
560
561 fp.close();
562 }
563
564 struct M3U_ListEntry
565 {
566 std::string path;
567 std::unique_ptr<std::string> name;
568 };
569
ReadM3U(std::vector<M3U_ListEntry> & file_list,size_t * default_cd,VirtualFS * vfs,std::string path,unsigned depth=0)570 static MDFN_COLD void ReadM3U(std::vector<M3U_ListEntry> &file_list, size_t* default_cd, VirtualFS* vfs, std::string path, unsigned depth = 0)
571 {
572 MemoryStream m3u_file(new FileStream(path, FileStream::MODE_READ));
573 std::string dir_path;
574 std::string linebuf;
575 std::unique_ptr<std::string> name;
576 int termc;
577
578 if(!m3u_file.read_utf8_bom())
579 {
580 m3u_file.mswin_utf8_convert_kludge();
581 }
582
583 vfs->get_file_path_components(path, &dir_path);
584
585 linebuf.reserve(2048);
586
587 while((termc = m3u_file.get_line(linebuf)) >= 0)
588 {
589 std::string efp;
590
591 MDFN_rtrim(&linebuf);
592
593 // Blank line, skip it.
594 if(linebuf.size() == 0)
595 continue;
596
597 // Comment line, skip it.
598 if(linebuf[0] == '#')
599 {
600 if(!strcmp(linebuf.c_str(), "#MEDNAFEN_DEFAULT"))
601 *default_cd = file_list.size();
602 else if(!strncmp(linebuf.c_str(), "#MEDNAFEN_LABEL", 15))
603 {
604 name.reset(new std::string(linebuf.substr(15)));
605 UTF8_sanitize(name.get());
606 MDFN_zapctrlchars(name.get());
607 MDFN_trim(name.get());
608 }
609 continue;
610 }
611
612 efp = vfs->eval_fip(dir_path, linebuf);
613
614 if(efp.size() >= 4 && !MDFN_strazicmp(efp.substr(efp.size() - 4).c_str(), ".m3u"))
615 {
616 if(efp == path)
617 throw(MDFN_Error(0, _("M3U at \"%s\" references self."), efp.c_str()));
618
619 if(depth == 99)
620 throw(MDFN_Error(0, _("M3U load recursion too deep!")));
621
622 ReadM3U(file_list, default_cd, vfs, efp, depth++);
623 }
624 else
625 file_list.emplace_back(M3U_ListEntry({efp, std::move(name)}));
626 }
627 }
628
PrintDiscsLayout(std::vector<CDInterface * > * ifaces)629 static MDFN_COLD void PrintDiscsLayout(std::vector<CDInterface *> *ifaces)
630 {
631 MDFN_AutoIndent aind(1);
632
633 for(unsigned i = 0; i < (*ifaces).size(); i++)
634 {
635 CDUtility::TOC toc;
636
637 (*ifaces)[i]->ReadTOC(&toc);
638
639 MDFN_printf(_("CD %u TOC:\n"), i + 1);
640 {
641 MDFN_AutoIndent aindd(1);
642 int32 eff_lt = 0;
643 const char* disc_type_string;
644
645 switch(toc.disc_type)
646 {
647 default:
648 disc_type_string = "";
649 break;
650
651 case CDUtility::DISC_TYPE_CDDA_OR_M1:
652 disc_type_string = _(" (CD-DA or Mode 1)");
653 break;
654
655 case CDUtility::DISC_TYPE_CD_I:
656 disc_type_string = _(" (CD-i)");
657 break;
658
659 case CDUtility::DISC_TYPE_CD_XA:
660 disc_type_string = _(" (CD-XA)");
661 break;
662 }
663
664 MDFN_printf(_("Disc Type: 0x%02x%s\n"), toc.disc_type, disc_type_string);
665 MDFN_printf(_("First Track: %2d\n"), toc.first_track);
666 MDFN_printf(_("Last Track: %2d\n"), toc.last_track);
667
668 for(int32 track = 1; track <= 99; track++)
669 {
670 if(!toc.tracks[track].valid)
671 continue;
672
673 eff_lt = track;
674
675 uint8 m, s, f;
676
677 CDUtility::LBA_to_AMSF(toc.tracks[track].lba, &m, &s, &f);
678
679 MDFN_printf(_("Track %2d, MSF: %02d:%02d:%02d, LBA: %6d %s%s\n"),
680 track,
681 m, s, f,
682 toc.tracks[track].lba,
683 (toc.tracks[track].control & 0x4) ? "DATA" : "AUDIO",
684 (track < toc.first_track || track > toc.last_track) ? _(" (Hidden)") : "");
685 }
686
687 MDFN_printf(_("Leadout: %6d %s\n"), toc.tracks[100].lba, (toc.tracks[100].control & 0x4) ? "DATA" : "AUDIO");
688
689 if((toc.tracks[eff_lt].control & 0x4) != (toc.tracks[100].control & 0x4))
690 MDFN_printf(_("WARNING: DATA/AUDIO TYPE MISMATCH BETWEEN LAST TRACK AND LEADOUT AREA."));
691
692 MDFN_printf("\n");
693 }
694 }
695 }
696
CalcDiscsLayoutMD5(std::vector<CDInterface * > * ifaces,uint8 out_md5[16])697 static MDFN_COLD void CalcDiscsLayoutMD5(std::vector<CDInterface *> *ifaces, uint8 out_md5[16])
698 {
699 md5_context layout_md5;
700
701 layout_md5.starts();
702
703 for(unsigned i = 0; i < (*ifaces).size(); i++)
704 {
705 CDUtility::TOC toc;
706
707 (*ifaces)[i]->ReadTOC(&toc);
708
709 layout_md5.update_u32_as_lsb(toc.first_track);
710 layout_md5.update_u32_as_lsb(toc.last_track);
711 layout_md5.update_u32_as_lsb(toc.tracks[100].lba);
712
713 for(uint32 track = 1; track <= 99; track++)
714 {
715 if(!toc.tracks[track].valid)
716 continue;
717
718 layout_md5.update_u32_as_lsb(toc.tracks[track].lba);
719 layout_md5.update_u32_as_lsb(toc.tracks[track].control & 0x4);
720 }
721 }
722
723 layout_md5.finish(out_md5);
724 }
725
LoadCustomPalette(void)726 static MDFN_COLD void LoadCustomPalette(void)
727 {
728 if(!MDFNGameInfo->CPInfo)
729 return;
730
731 for(auto cpi = MDFNGameInfo->CPInfo; cpi->description || cpi->name_override; cpi++)
732 {
733 if(!(MDFNGameInfo->CPInfoActiveBF & (1U << (cpi - MDFNGameInfo->CPInfo))))
734 continue;
735
736 std::string colormap_fn = MDFN_MakeFName(MDFNMKF_PALETTE, 0, cpi->name_override);
737
738 MDFN_printf("\n");
739 MDFN_printf(_("Loading custom palette from \"%s\"...\n"), colormap_fn.c_str());
740 {
741 MDFN_AutoIndent aind(1);
742
743 try
744 {
745 FileStream fp(colormap_fn, FileStream::MODE_READ);
746 const uint64 fpsz = fp.size();
747
748 for(auto vec = cpi->valid_entry_count; *vec; vec++)
749 {
750 if(fpsz == *vec * 3)
751 {
752 CustomPaletteNumEntries = *vec;
753 CustomPalette = new uint8[CustomPaletteNumEntries * 3];
754
755 fp.read(CustomPalette, CustomPaletteNumEntries * 3);
756
757 return;
758 }
759 }
760
761 //
762 // File size is not valid, print out an error message with helpful information.
763 //
764 std::string vfszs;
765 for(auto vec = cpi->valid_entry_count; *vec; vec++)
766 {
767 if(vfszs.size())
768 vfszs += _(", ");
769
770 vfszs += std::to_string(3 * *vec);
771 }
772
773 throw MDFN_Error(0, _("Custom palette file's size(%llu bytes) is incorrect. Valid sizes are: %s"), (unsigned long long)fpsz, vfszs.c_str());
774 }
775 catch(MDFN_Error &e)
776 {
777 MDFN_printf(_("Error: %s\n"), e.what());
778
779 if(e.GetErrno() != ENOENT)
780 throw;
781
782 return;
783 }
784 catch(std::exception &e)
785 {
786 MDFN_printf(_("Error: %s\n"), e.what());
787 throw;
788 }
789 }
790 break;
791 }
792 }
793
LoadCommonPost(VirtualFS * vfs,const char * path)794 static MDFN_COLD void LoadCommonPost(VirtualFS* vfs, const char* path)
795 {
796 DMStatus.resize(MDFNGameInfo->RMD->Drives.size());
797 DMStatusSaveStateTemp.resize(DMStatus.size());
798
799 DMSNoMedia.clear();
800 for(uint32 drive_idx = 0; drive_idx < MDFNGameInfo->RMD->Drives.size(); drive_idx++)
801 {
802 const RMD_Drive& drive = MDFNGameInfo->RMD->Drives[drive_idx];
803
804 for(uint32 state_idx = 0; state_idx < drive.PossibleStates.size(); state_idx++)
805 {
806 const RMD_State& state = drive.PossibleStates[state_idx];
807 if(!state.MediaPresent)
808 {
809 DMSNoMedia.push_back(state_idx);
810 break;
811 }
812 }
813 assert(DMSNoMedia.size() == drive_idx + 1);
814 }
815 //
816 //
817 if(MDFNGameInfo->name.size() == 0 && path)
818 {
819 vfs->get_file_path_components(path, NULL, &MDFNGameInfo->name);
820
821 for(auto& c : MDFNGameInfo->name)
822 if(c == '_' || (uint8)c < 0x20)
823 c = ' ';
824
825 MDFN_trim(&MDFNGameInfo->name);
826 }
827
828 //
829 //
830 //
831
832 LoadCustomPalette();
833 if(MDFNGameInfo->GameType != GMT_PLAYER)
834 {
835 MDFN_LoadGameCheats(NULL);
836 MDFNMP_InstallReadPatches();
837 }
838
839 MDFNI_SetLayerEnableMask(~0ULL);
840
841 #ifdef WANT_DEBUGGER
842 MDFNDBG_PostGameLoad();
843 #endif
844
845 MDFNSS_CheckStates();
846 MDFNMOV_CheckMovies();
847
848 PrevInterlaced = false;
849 SettingChanged("video.deinterlacer");
850
851 TBlur_Init();
852
853 MDFNSRW_Begin();
854
855 LastSoundMultiplier = 1;
856 last_sound_rate = -1;
857 last_pixel_format = MDFN_PixelFormat();
858 }
859
LoadCD(const char * force_module,VirtualFS * vfs,const char * path,CDInterface * cdif=nullptr)860 static MDFNGI *LoadCD(const char *force_module, VirtualFS* vfs, const char *path, CDInterface* cdif = nullptr)
861 {
862 std::vector<M3U_ListEntry> file_list;
863 uint8 LayoutMD5[16];
864 size_t default_cd = 0;
865
866 try
867 {
868 MDFN_AutoIndent aind(1);
869 const bool image_memcache = MDFN_GetSettingB("cd.image_memcache");
870 const uint64 affinity = MDFN_GetSettingUI("affinity.cd");
871
872 if(cdif)
873 {
874 file_list.emplace_back(M3U_ListEntry({ path }));
875 CDInterfaces.resize(1);
876 CDInterfaces[0] = cdif;
877 }
878 else
879 {
880 if(strlen(path) > 4 && !MDFN_strazicmp(path + strlen(path) - 4, ".m3u"))
881 ReadM3U(file_list, &default_cd, vfs, path);
882 else
883 file_list.emplace_back(M3U_ListEntry({ path }));
884
885 CDInterfaces.resize(file_list.size());
886 for(size_t i = 0; i < file_list.size(); i++)
887 CDInterfaces[i] = CDInterface::Open(vfs, file_list[i].path, image_memcache, affinity);
888 }
889
890 GetFileBase(path);
891 }
892 catch(std::exception &e)
893 {
894 MDFN_Notify(MDFN_NOTICE_ERROR, _("Error opening CD: %s"), e.what());
895
896 for(unsigned i = 0; i < CDInterfaces.size(); i++)
897 {
898 if(CDInterfaces[i])
899 {
900 delete CDInterfaces[i];
901 CDInterfaces[i] = NULL;
902 }
903 }
904 CDInterfaces.clear();
905
906 MDFNGameInfo = NULL;
907
908 return(NULL);
909 }
910
911 MDFN_printf("\n");
912
913 //
914 // Print out a track list for all discs.
915 //
916 PrintDiscsLayout(&CDInterfaces);
917
918 //
919 // Calculate layout MD5. The system emulation LoadCD() code is free to ignore this value and calculate
920 // its own, or to use it to look up a game in its database.
921 //
922 CalcDiscsLayoutMD5(&CDInterfaces, LayoutMD5);
923
924 try
925 {
926 std::unique_ptr<RMD_Layout> rmd(new RMD_Layout());
927 MDFNGameInfo = NULL;
928
929 {
930 RMD_Drive dr;
931
932 dr.Name = "Virtual CD Drive";
933 dr.PossibleStates.push_back(RMD_State({"Tray Open", false, false, true}));
934 dr.PossibleStates.push_back(RMD_State({"Tray Closed (Empty)", false, false, false}));
935 dr.PossibleStates.push_back(RMD_State({"Tray Closed", true, true, false}));
936 dr.CompatibleMedia.push_back(0);
937 dr.MediaMtoPDelay = 2000;
938
939 rmd->Drives.push_back(dr);
940 rmd->DrivesDefaults.push_back(RMD_DriveDefaults({0, 0, 0}));
941 rmd->MediaTypes.push_back(RMD_MediaType({"CD"}));
942 }
943
944 for(size_t i = 0; i < CDInterfaces.size(); i++)
945 {
946 if(i == default_cd)
947 {
948 rmd->DrivesDefaults[0].State = 2; // Tray Closed
949 rmd->DrivesDefaults[0].Media = i;
950 rmd->DrivesDefaults[0].Orientation = 0;
951 }
952
953 if(file_list[i].name)
954 rmd->Media.push_back(RMD_Media({std::string(1, '"') + *file_list[i].name + '"', 0}));
955 else
956 {
957 char namebuf[128];
958 trio_snprintf(namebuf, sizeof(namebuf), _("Disc %zu of %zu"), i + 1, CDInterfaces.size());
959 rmd->Media.push_back(RMD_Media({namebuf, 0}));
960 }
961 }
962
963 for(std::list<MDFNGI *>::iterator it = MDFNSystemsPrio.begin(); it != MDFNSystemsPrio.end(); it++) //_unsigned int x = 0; x < MDFNSystems.size(); x++)
964 {
965 if(force_module)
966 {
967 if(!strcmp(force_module, (*it)->shortname))
968 {
969 MDFNGameInfo = *it;
970 break;
971 }
972 }
973 else
974 {
975 char tmpstr[256];
976 trio_snprintf(tmpstr, 256, "%s.enable", (*it)->shortname);
977
978 // Is module enabled?
979 if(!MDFN_GetSettingB(tmpstr))
980 {
981 MDFN_printf(_("Skipping module \"%s\" per \"%s\" setting.\n"), (*it)->shortname, tmpstr);
982 continue;
983 }
984
985 if(!(*it)->LoadCD || !(*it)->TestMagicCD)
986 continue;
987
988 if((*it)->TestMagicCD(&CDInterfaces))
989 {
990 MDFNGameInfo = *it;
991 break;
992 }
993 }
994 }
995
996 if(!MDFNGameInfo)
997 {
998 if(force_module)
999 throw MDFN_Error(0, _("Unrecognized system \"%s\"!"), force_module);
1000 else
1001 throw MDFN_Error(0, _("Could not find a system that supports this CD."));
1002 }
1003
1004 // This if statement will be true if force_module references a system without CDROM support.
1005 if(!MDFNGameInfo->LoadCD)
1006 throw MDFN_Error(0, _("Specified system \"%s\" doesn't support CDs!"), force_module);
1007
1008 MDFN_printf(_("Using module: %s(%s)\n"), MDFNGameInfo->shortname, MDFNGameInfo->fullname);
1009 {
1010 MDFN_AutoIndent aindentgm(1);
1011
1012 assert(MDFNGameInfo->soundchan != 0);
1013
1014 MDFNGameInfo->name.clear();
1015 MDFNGameInfo->DesiredInput.clear();
1016 MDFNGameInfo->rotated = 0;
1017 MDFNGameInfo->RMD = rmd.get();
1018
1019 memcpy(MDFNGameInfo->MD5, LayoutMD5, 16);
1020
1021 {
1022 std::string modoverride_settings_file_path = MDFN_GetBaseDirectory() + PSS + MDFNGameInfo->shortname + ".cfg";
1023 MDFN_LoadSettings(modoverride_settings_file_path.c_str(), true);
1024 }
1025 {
1026 std::string pgcoverride_settings_file_path = MDFN_MakeFName(MDFNMKF_PGCONFIG, 0, NULL);
1027 MDFN_LoadSettings(pgcoverride_settings_file_path.c_str(), true);
1028 }
1029
1030 MDFN_printf("\n");
1031
1032 MDFNGameInfo->LoadCD(&CDInterfaces);
1033 }
1034
1035 LoadCommonPost(vfs, path);
1036
1037 //
1038 //
1039 rmd.release();
1040 }
1041 catch(std::exception &e)
1042 {
1043 MDFN_Notify(MDFN_NOTICE_ERROR, "%s", e.what());
1044
1045 for(unsigned i = 0; i < CDInterfaces.size(); i++)
1046 {
1047 if(CDInterfaces[i])
1048 {
1049 delete CDInterfaces[i];
1050 CDInterfaces[i] = NULL;
1051 }
1052 }
1053 CDInterfaces.clear();
1054
1055 if(MDFNGameInfo != NULL)
1056 {
1057 memset(MDFNGameInfo->MD5, 0, sizeof(MDFNGameInfo->MD5));
1058 MDFNGameInfo->RMD = NULL;
1059 MDFNGameInfo = NULL;
1060 }
1061
1062 if(CustomPalette != NULL)
1063 {
1064 delete[] CustomPalette;
1065 CustomPalette = NULL;
1066 }
1067 CustomPaletteNumEntries = 0;
1068
1069 MDFN_ClearAllOverrideSettings();
1070
1071 return NULL;
1072 }
1073
1074 return MDFNGameInfo;
1075 }
1076
MDFNI_LoadExternalCD(const char * force_module,const char * path_hint,CDInterface * cdif)1077 MDFNGI *MDFNI_LoadExternalCD(const char* force_module, const char* path_hint, CDInterface* cdif)
1078 {
1079 return LoadCD(force_module, &::Mednafen::NVFS, path_hint, cdif);
1080 }
1081
LoadIPS(MDFNFILE * mfgf,const std::string & path)1082 static MDFN_COLD void LoadIPS(MDFNFILE* mfgf, const std::string& path)
1083 {
1084 MDFN_printf(_("Applying IPS file \"%s\"...\n"), path.c_str());
1085
1086 try
1087 {
1088 FileStream IPSFile(path, FileStream::MODE_READ);
1089
1090 mfgf->ApplyIPS(&IPSFile);
1091 }
1092 catch(MDFN_Error &e)
1093 {
1094 MDFN_indent(1);
1095 MDFN_printf(_("Failed: %s\n"), e.what());
1096 MDFN_indent(-1);
1097 if(e.GetErrno() != ENOENT)
1098 throw;
1099 }
1100 catch(std::exception &e)
1101 {
1102 MDFN_indent(1);
1103 MDFN_printf(_("Failed: %s\n"), e.what());
1104 MDFN_indent(-1);
1105 throw;
1106 }
1107 }
1108
IsModuleEnabled(MDFNGI * gi)1109 static bool IsModuleEnabled(MDFNGI* gi)
1110 {
1111 char tmpstr[256];
1112 trio_snprintf(tmpstr, 256, "%s.enable", gi->shortname);
1113
1114 // Is module enabled?
1115 return MDFN_GetSettingB(tmpstr);
1116 }
1117
FindCompatibleModule(const char * force_module,GameFile * gf)1118 static MDFN_COLD MDFNGI* FindCompatibleModule(const char* force_module, GameFile* gf)
1119 {
1120 //for(unsigned pass = 0; pass < 2; pass++)
1121 //{
1122 for(std::list<MDFNGI *>::iterator it = MDFNSystemsPrio.begin(); it != MDFNSystemsPrio.end(); it++) //_unsigned int x = 0; x < MDFNSystems.size(); x++)
1123 {
1124 if(force_module)
1125 {
1126 if(!strcmp(force_module, (*it)->shortname))
1127 {
1128 if(!(*it)->Load)
1129 {
1130 if((*it)->LoadCD)
1131 throw MDFN_Error(0, _("Specified system only supports CD(physical, or image files, such as *.cue and *.toc) loading."));
1132 else
1133 throw MDFN_Error(0, _("Specified system does not support normal file loading."));
1134 }
1135 return(*it);
1136 }
1137 }
1138 else
1139 {
1140 char tmpstr[256];
1141 trio_snprintf(tmpstr, 256, "%s.enable", (*it)->shortname);
1142
1143 // Is module enabled?
1144 if(!MDFN_GetSettingB(tmpstr))
1145 {
1146 MDFN_printf(_("Skipping module \"%s\" per \"%s\" setting.\n"), (*it)->shortname, tmpstr);
1147 continue;
1148 }
1149
1150 if(!(*it)->Load || !(*it)->TestMagic)
1151 continue;
1152
1153 gf->stream->rewind();
1154
1155 if((*it)->TestMagic(gf))
1156 {
1157 return(*it);
1158 }
1159 }
1160 }
1161 //}
1162
1163 return(NULL);
1164 }
1165
MDFNI_LoadGame(const char * force_module,VirtualFS * vfs,const char * path,bool force_cd)1166 MDFNGI *MDFNI_LoadGame(const char *force_module, VirtualFS* vfs, const char* path, bool force_cd)
1167 {
1168 assert(path != nullptr);
1169 const size_t path_len = strlen(path);
1170
1171 MDFNI_CloseGame();
1172
1173 MDFN_printf(_("Loading %s...\n"), path);
1174
1175 if(force_cd || (path_len > 4 && (!MDFN_strazicmp(path + path_len - 4, ".cue") || !MDFN_strazicmp(path + path_len - 4, ".toc") || !MDFN_strazicmp(path + path_len - 4, ".ccd") || !MDFN_strazicmp(path + path_len - 4, ".m3u"))))
1176 {
1177 return LoadCD(force_module, vfs, path);
1178 }
1179
1180 try
1181 {
1182 std::vector<FileExtensionSpecStruct> valid_iae;
1183
1184 // Construct a list of known file extensions for MDFNFILE
1185 for(unsigned int i = 0; i < MDFNSystems.size(); i++)
1186 {
1187 const FileExtensionSpecStruct *curexts = MDFNSystems[i]->FileExtensions;
1188
1189 // If we're forcing a module, only look for extensions corresponding to that module
1190 if(force_module && strcmp(MDFNSystems[i]->shortname, force_module))
1191 continue;
1192
1193 if(!force_module && !IsModuleEnabled(MDFNSystems[i]))
1194 continue;
1195
1196 if(curexts)
1197 {
1198 while(curexts->extension && curexts->description)
1199 {
1200 valid_iae.push_back(*curexts);
1201 curexts++;
1202 }
1203 }
1204 }
1205
1206 /*
1207 //
1208 // CD format extensions; refer to git.h for priorities.
1209 //
1210 valid_iae.push_back(FileExtensionSpecStruct({ "m3u", -40, "M3U" }));
1211 valid_iae.push_back(FileExtensionSpecStruct({ "ccd", -50, "CloneCD" }));
1212 valid_iae.push_back(FileExtensionSpecStruct({ "cue", -60, "CUE" }));
1213 valid_iae.push_back(FileExtensionSpecStruct({ "toc", -70, "CDRDAO TOC" }));
1214 */
1215
1216 MDFNFILE mfgf(vfs, path, valid_iae, _("game"));
1217
1218 /*
1219 if(mfgf.active_vfs() != vfs && (mfgf.ext == "m3u" || mfgf.ext == "ccd" || mfgf.ext == "cue" || mfgf.ext == "toc"))
1220 {
1221 static std::unique_ptr<VirtualFS> vfs_save;
1222
1223 vfs_save = mfgf.steal_archive_vfs();
1224
1225 if(!MDFN_GetSettingB("cd.image_memcache"))
1226 throw MDFN_Error(0, _("Setting \"cd.image_memcache\" must be set to \"1\" to allow loading a CD image from a ZIP archive."));
1227
1228 return LoadCD(force_module, vfs_save.get(), mfgf.active_path().c_str());
1229 }
1230 */
1231 //printf("FBASE=%s,EXT=%s\n", GameFile.fbase, GameFile.ext);
1232 //
1233 //
1234 //
1235 //
1236 MDFN_AutoIndent aind(1);
1237 std::unique_ptr<RMD_Layout> rmd(new RMD_Layout());
1238
1239 MDFNGameInfo = NULL;
1240
1241 GetFileBase(path);
1242 //
1243 //
1244 //
1245
1246 LoadIPS(&mfgf, MDFN_MakeFName(MDFNMKF_IPS, 0, 0));
1247
1248 //
1249 std::string outside_dir, outside_fbase;
1250 vfs->get_file_path_components(path, &outside_dir, &outside_fbase);
1251 //
1252 GameFile gf({ mfgf.active_vfs(), mfgf.active_dir_path(), mfgf.stream(), mfgf.ext, mfgf.fbase, vfs, outside_dir, outside_fbase });
1253
1254 MDFNGameInfo = FindCompatibleModule(force_module, &gf);
1255
1256 if(!MDFNGameInfo)
1257 {
1258 if(force_module)
1259 throw MDFN_Error(0, _("Unrecognized system \"%s\"!"), force_module);
1260 else
1261 throw MDFN_Error(0, _("Unrecognized file format."));
1262 }
1263
1264 MDFN_printf(_("Using module: %s(%s)\n"), MDFNGameInfo->shortname, MDFNGameInfo->fullname);
1265 {
1266 MDFN_AutoIndent aindentgm(1);
1267
1268 assert(MDFNGameInfo->soundchan != 0);
1269
1270 MDFNGameInfo->name.clear();
1271 MDFNGameInfo->DesiredInput.clear();
1272 MDFNGameInfo->rotated = 0;
1273 MDFNGameInfo->RMD = rmd.get();
1274
1275 {
1276 std::string modoverride_settings_file_path = MDFN_GetBaseDirectory() + PSS + MDFNGameInfo->shortname + ".cfg";
1277 MDFN_LoadSettings(modoverride_settings_file_path.c_str(), true);
1278 }
1279 {
1280 std::string pgcoverride_settings_file_path = MDFN_MakeFName(MDFNMKF_PGCONFIG, 0, NULL);
1281 MDFN_LoadSettings(pgcoverride_settings_file_path.c_str(), true);
1282 }
1283
1284 MDFN_printf("\n");
1285
1286 gf.stream->rewind();
1287 MDFNGameInfo->Load(&gf);
1288 }
1289
1290 LoadCommonPost(vfs, path);
1291 rmd.release();
1292 }
1293 catch(std::exception &e)
1294 {
1295 MDFN_Notify(MDFN_NOTICE_ERROR, "%s", e.what());
1296
1297 if(MDFNGameInfo != NULL)
1298 {
1299 MDFNGameInfo->RMD = NULL;
1300 MDFNGameInfo = NULL;
1301 }
1302
1303 if(CustomPalette != NULL)
1304 {
1305 delete[] CustomPalette;
1306 CustomPalette = NULL;
1307 }
1308 CustomPaletteNumEntries = 0;
1309
1310 MDFN_ClearAllOverrideSettings();
1311
1312 return(NULL);
1313 }
1314
1315 return(MDFNGameInfo);
1316 }
1317
BuildDynamicSetting(MDFNSetting * setting,const char * system_name,const char * name,uint32 flags,const char * description,MDFNSettingType type,const char * default_value,const char * minimum=NULL,const char * maximum=NULL,bool (* validate_func)(const char * name,const char * value)=NULL,void (* ChangeNotification)(const char * name)=NULL)1318 static void BuildDynamicSetting(MDFNSetting *setting, const char *system_name, const char *name, uint32 flags, const char *description, MDFNSettingType type,
1319 const char *default_value, const char *minimum = NULL, const char *maximum = NULL,
1320 bool (*validate_func)(const char *name, const char *value) = NULL, void (*ChangeNotification)(const char *name) = NULL)
1321 {
1322 char setting_name[256];
1323
1324 memset(setting, 0, sizeof(MDFNSetting));
1325
1326 trio_snprintf(setting_name, 256, "%s.%s", system_name, name);
1327
1328 setting->name = strdup(setting_name);
1329 setting->description = description;
1330 setting->type = type;
1331 setting->flags = flags;
1332 setting->default_value = default_value;
1333 setting->minimum = minimum;
1334 setting->maximum = maximum;
1335 setting->validate_func = validate_func;
1336 setting->ChangeNotification = ChangeNotification;
1337 }
1338
MDFNI_InitializeModules(void)1339 bool MDFNI_InitializeModules(void)
1340 {
1341 Time::Time_Init();
1342 CDUtility::CDUtility_Init();
1343 //
1344 //
1345 //
1346 static MDFNGI *InternalSystems[] =
1347 {
1348 #ifdef WANT_APPLE2_EMU
1349 &EmulatedApple2,
1350 #endif
1351
1352 #ifdef WANT_NES_EMU
1353 &EmulatedNES,
1354 #endif
1355
1356 #ifdef WANT_NES_NEW_EMU
1357 &EmulatedNES_New,
1358 #endif
1359
1360 #ifdef WANT_SNES_EMU
1361 &EmulatedSNES,
1362 #endif
1363
1364 #ifdef WANT_SNES_FAUST_EMU
1365 &EmulatedSNES_Faust,
1366 #endif
1367
1368 #ifdef WANT_GB_EMU
1369 &EmulatedGB,
1370 #endif
1371
1372 #ifdef WANT_GBA_EMU
1373 &EmulatedGBA,
1374 #endif
1375
1376 #ifdef WANT_PCE_EMU
1377 &EmulatedPCE,
1378 #endif
1379
1380 #ifdef WANT_PCE_FAST_EMU
1381 &EmulatedPCE_Fast,
1382 #endif
1383
1384 #ifdef WANT_LYNX_EMU
1385 &EmulatedLynx,
1386 #endif
1387
1388 #ifdef WANT_MD_EMU
1389 &EmulatedMD,
1390 #endif
1391
1392 #ifdef WANT_PCFX_EMU
1393 &EmulatedPCFX,
1394 #endif
1395
1396 #ifdef WANT_NGP_EMU
1397 &EmulatedNGP,
1398 #endif
1399
1400 #ifdef WANT_PSX_EMU
1401 &EmulatedPSX,
1402 #endif
1403
1404 #ifdef WANT_SS_EMU
1405 &EmulatedSS,
1406 #endif
1407
1408 #ifdef WANT_SSFPLAY_EMU
1409 &EmulatedSSFPlay,
1410 #endif
1411
1412 #ifdef WANT_VB_EMU
1413 &EmulatedVB,
1414 #endif
1415
1416 #ifdef WANT_WSWAN_EMU
1417 &EmulatedWSwan,
1418 #endif
1419
1420 #ifdef WANT_SMS_EMU
1421 &EmulatedSMS,
1422 &EmulatedGG,
1423 #endif
1424
1425 &EmulatedCDPlay,
1426 &EmulatedDEMO
1427 };
1428 static_assert(MEDNAFEN_VERSION_NUMERIC >= 0x00102601, "Bad MEDNAFEN_VERSION_NUMERIC");
1429
1430 for(unsigned int i = 0; i < sizeof(InternalSystems) / sizeof(MDFNGI *); i++)
1431 AddSystem(InternalSystems[i]);
1432
1433 for(unsigned int i = 0; i < MDFNSystems.size(); i++)
1434 MDFNSystemsPrio.push_back(MDFNSystems[i]);
1435
1436 MDFNSystemsPrio.sort(MDFNSystemsPrio_CompareFunc);
1437 //
1438 //
1439 //
1440 std::string modules_string;
1441 for(auto& m : MDFNSystemsPrio)
1442 {
1443 if(modules_string.size())
1444 modules_string += " ";
1445 modules_string += m->shortname;
1446 }
1447 MDFNI_printf(_("Emulation modules: %s\n"), modules_string.c_str());
1448
1449 return(1);
1450 }
1451
MDFNI_Initialize(const char * basedir,const std::vector<MDFNSetting> & DriverSettings)1452 int MDFNI_Initialize(const char *basedir, const std::vector<MDFNSetting> &DriverSettings)
1453 {
1454 // FIXME static
1455 static std::vector<MDFNSetting> dynamic_settings;
1456
1457 // DO NOT REMOVE/DISABLE THESE MATH AND COMPILER SANITY TESTS. THEY EXIST FOR A REASON.
1458 //uint64 st = Time::MonoUS();
1459 if(!MDFN_RunMathTests())
1460 {
1461 return(0);
1462 }
1463 //printf("tests time: %llu\n", Time::MonoUS() - st);
1464
1465 for(unsigned x = 0; x < 16; x++)
1466 {
1467 PortDevice[x] = ~0U;
1468 PortData[x] = NULL;
1469 PortDataLen[x] = 0;
1470 }
1471
1472 lzo_init();
1473
1474 MDFN_SetBaseDirectory(basedir);
1475
1476 MDFN_InitFontData();
1477
1478 // Generate dynamic settings
1479 for(unsigned int i = 0; i < MDFNSystems.size(); i++)
1480 {
1481 MDFNSetting setting;
1482 const char *sysname;
1483
1484 sysname = (const char *)MDFNSystems[i]->shortname;
1485
1486 if(!MDFNSystems[i]->soundchan)
1487 printf("0 sound channels for %s????\n", sysname);
1488
1489 if(MDFNSystems[i]->soundchan == 2)
1490 {
1491 BuildDynamicSetting(&setting, sysname, "forcemono", MDFNSF_COMMON_TEMPLATE | MDFNSF_CAT_SOUND, CSD_forcemono, MDFNST_BOOL, "0");
1492 dynamic_settings.push_back(setting);
1493 }
1494
1495 BuildDynamicSetting(&setting, sysname, "enable", MDFNSF_COMMON_TEMPLATE, CSD_enable, MDFNST_BOOL, "1");
1496 dynamic_settings.push_back(setting);
1497
1498 BuildDynamicSetting(&setting, sysname, "tblur", MDFNSF_COMMON_TEMPLATE | MDFNSF_CAT_VIDEO, CSD_tblur, MDFNST_BOOL, "0");
1499 dynamic_settings.push_back(setting);
1500
1501 BuildDynamicSetting(&setting, sysname, "tblur.accum", MDFNSF_COMMON_TEMPLATE | MDFNSF_CAT_VIDEO, CSD_tblur_accum, MDFNST_BOOL, "0");
1502 dynamic_settings.push_back(setting);
1503
1504 BuildDynamicSetting(&setting, sysname, "tblur.accum.amount", MDFNSF_COMMON_TEMPLATE | MDFNSF_CAT_VIDEO, CSD_tblur_accum_amount, MDFNST_FLOAT, "50", "0", "100");
1505 dynamic_settings.push_back(setting);
1506 }
1507
1508 // First merge all settable settings, then load the settings from the SETTINGS FILE OF DOOOOM
1509 MDFN_MergeSettings(MednafenSettings);
1510 MDFN_MergeSettings(dynamic_settings);
1511 MDFN_MergeSettings(MDFNMP_Settings);
1512
1513 if(DriverSettings.size())
1514 MDFN_MergeSettings(DriverSettings);
1515
1516 for(unsigned int x = 0; x < MDFNSystems.size(); x++)
1517 {
1518 if(MDFNSystems[x]->Settings)
1519 MDFN_MergeSettings(MDFNSystems[x]->Settings);
1520 }
1521
1522 MDFN_MergeSettings(RenamedSettings);
1523
1524 MDFN_FinalizeSettings();
1525
1526 #ifdef WANT_DEBUGGER
1527 MDFNDBG_Init();
1528 #endif
1529
1530 return(1);
1531 }
1532
MDFNI_LoadSettings(const char * path)1533 int MDFNI_LoadSettings(const char* path)
1534 {
1535 try
1536 {
1537 if(!MDFN_LoadSettings(path))
1538 return -1;
1539 }
1540 catch(std::exception &e)
1541 {
1542 MDFN_Notify(MDFN_NOTICE_ERROR, "%s", e.what());
1543 return 0;
1544 }
1545
1546 return 1;
1547 }
1548
MDFNI_SaveSettings(const char * path)1549 bool MDFNI_SaveSettings(const char* path)
1550 {
1551 try
1552 {
1553 MDFN_SaveSettings(path);
1554 }
1555 catch(std::exception &e)
1556 {
1557 MDFN_Notify(MDFN_NOTICE_ERROR, "%s", e.what());
1558 return false;
1559 }
1560 return true;
1561 }
1562
MDFNI_Kill(void)1563 void MDFNI_Kill(void)
1564 {
1565 MDFN_KillSettings();
1566 }
1567
1568 static double multiplier_save, volume_save;
1569 static std::vector<int16> SoundBufPristine;
1570
ProcessAudio(EmulateSpecStruct * espec)1571 static void ProcessAudio(EmulateSpecStruct *espec)
1572 {
1573 if(espec->SoundVolume != 1)
1574 volume_save = espec->SoundVolume;
1575
1576 if(espec->soundmultiplier != 1)
1577 multiplier_save = espec->soundmultiplier;
1578
1579 if(espec->SoundBuf && espec->SoundBufSize)
1580 {
1581 int16 *const SoundBuf = espec->SoundBuf + espec->SoundBufSize_InternalProcessed * MDFNGameInfo->soundchan;
1582 int32 SoundBufSize = espec->SoundBufSize - espec->SoundBufSize_InternalProcessed;
1583 const int32 SoundBufMaxSize = espec->SoundBufMaxSize - espec->SoundBufSize_InternalProcessed;
1584
1585 //
1586 // Sound reverse code goes before copying sound data to SoundBufPristine.
1587 //
1588 if(espec->NeedSoundReverse)
1589 {
1590 int16 *yaybuf = SoundBuf;
1591 int32 slen = SoundBufSize;
1592
1593 if(MDFNGameInfo->soundchan == 1)
1594 {
1595 for(int x = 0; x < (slen / 2); x++)
1596 {
1597 int16 cha = yaybuf[slen - x - 1];
1598 yaybuf[slen - x - 1] = yaybuf[x];
1599 yaybuf[x] = cha;
1600 }
1601 }
1602 else if(MDFNGameInfo->soundchan == 2)
1603 {
1604 for(int x = 0; x < (slen * 2) / 2; x++)
1605 {
1606 int16 cha = yaybuf[slen * 2 - (x&~1) - ((x&1) ^ 1) - 1];
1607 yaybuf[slen * 2 - (x&~1) - ((x&1) ^ 1) - 1] = yaybuf[x];
1608 yaybuf[x] = cha;
1609 }
1610 }
1611 }
1612
1613
1614 if(qtrecorder && (volume_save != 1 || multiplier_save != 1))
1615 {
1616 int32 orig_size = SoundBufPristine.size();
1617
1618 SoundBufPristine.resize(orig_size + SoundBufSize * MDFNGameInfo->soundchan);
1619 for(int i = 0; i < SoundBufSize * MDFNGameInfo->soundchan; i++)
1620 SoundBufPristine[orig_size + i] = SoundBuf[i];
1621 }
1622
1623 try
1624 {
1625 if(wavrecorder)
1626 wavrecorder->WriteSound(SoundBuf, SoundBufSize);
1627 }
1628 catch(std::exception &e)
1629 {
1630 MDFND_OutputNotice(MDFN_NOTICE_ERROR, e.what());
1631 delete wavrecorder;
1632 wavrecorder = NULL;
1633 }
1634
1635 if(multiplier_save != LastSoundMultiplier)
1636 {
1637 ff_resampler.time_ratio(multiplier_save, 0.9965);
1638 LastSoundMultiplier = multiplier_save;
1639 }
1640
1641 if(multiplier_save != 1)
1642 {
1643 if(FFDiscard)
1644 {
1645 if(SoundBufSize >= multiplier_save)
1646 SoundBufSize /= multiplier_save;
1647 }
1648 else
1649 {
1650 if(MDFNGameInfo->soundchan == 2)
1651 {
1652 assert(ff_resampler.max_write() >= SoundBufSize * 2);
1653
1654 for(int i = 0; i < SoundBufSize * 2; i++)
1655 ff_resampler.buffer()[i] = SoundBuf[i];
1656 }
1657 else
1658 {
1659 assert(ff_resampler.max_write() >= SoundBufSize * 2);
1660
1661 for(int i = 0; i < SoundBufSize; i++)
1662 {
1663 ff_resampler.buffer()[i * 2] = SoundBuf[i];
1664 ff_resampler.buffer()[i * 2 + 1] = 0;
1665 }
1666 }
1667 ff_resampler.write(SoundBufSize * 2);
1668
1669 int avail = ff_resampler.avail();
1670 int real_read = std::min((int)(SoundBufMaxSize * MDFNGameInfo->soundchan), avail);
1671
1672 if(MDFNGameInfo->soundchan == 2)
1673 SoundBufSize = ff_resampler.read(SoundBuf, real_read ) >> 1;
1674 else
1675 SoundBufSize = ff_resampler.read_mono_hack(SoundBuf, real_read );
1676
1677 avail -= real_read;
1678
1679 if(avail > 0)
1680 {
1681 printf("ff_resampler.avail() > espec->SoundBufMaxSize * MDFNGameInfo->soundchan - %d\n", avail);
1682 ff_resampler.clear();
1683 }
1684 }
1685 }
1686
1687 if(volume_save != 1)
1688 {
1689 if(volume_save < 1)
1690 {
1691 int volume = (int)(16384 * volume_save);
1692
1693 for(int i = 0; i < SoundBufSize * MDFNGameInfo->soundchan; i++)
1694 SoundBuf[i] = (SoundBuf[i] * volume) >> 14;
1695 }
1696 else
1697 {
1698 int volume = (int)(256 * volume_save);
1699
1700 for(int i = 0; i < SoundBufSize * MDFNGameInfo->soundchan; i++)
1701 {
1702 int temp = ((SoundBuf[i] * volume) >> 8) + 32768;
1703
1704 temp = clamp_to_u16(temp);
1705
1706 SoundBuf[i] = temp - 32768;
1707 }
1708 }
1709 }
1710
1711 // TODO: Optimize this.
1712 if(MDFNGameInfo->soundchan == 2 && MDFN_GetSettingB(std::string(MDFNGameInfo->shortname) + ".forcemono"))
1713 {
1714 for(int i = 0; i < SoundBufSize * MDFNGameInfo->soundchan; i += 2)
1715 {
1716 // We should use division instead of arithmetic right shift for correctness(rounding towards 0 instead of negative infinitininintinity), but I like speed.
1717 int32 mixed = (SoundBuf[i + 0] + SoundBuf[i + 1]) >> 1;
1718
1719 SoundBuf[i + 0] =
1720 SoundBuf[i + 1] = mixed;
1721 }
1722 }
1723
1724 espec->SoundBufSize = espec->SoundBufSize_InternalProcessed + SoundBufSize;
1725 } // end to: if(espec->SoundBuf && espec->SoundBufSize)
1726 }
1727
MDFN_MidSync(EmulateSpecStruct * espec,const unsigned flags)1728 void MDFN_MidSync(EmulateSpecStruct *espec, const unsigned flags)
1729 {
1730 ProcessAudio(espec);
1731 espec->SoundBufSize_InternalProcessed = espec->SoundBufSize;
1732 espec->MasterCycles_InternalProcessed = espec->MasterCycles;
1733 //
1734 // We could act as if flags = 0 during netplay, and call MDFND_MidSync(), but
1735 // we'd need to fix the kludgy driver-side code that handles sound buffer underruns.
1736 //
1737 if(!MDFNnetplay)
1738 {
1739 MDFND_MidSync(espec, flags);
1740 //
1741 if((flags & MIDSYNC_FLAG_UPDATE_INPUT) && MDFNGameInfo->TransformInput) // Call after MDFND_MidSync, and before MDFNMOV_ProcessInput
1742 MDFNGameInfo->TransformInput();
1743 }
1744
1745 if(flags & MIDSYNC_FLAG_UPDATE_INPUT)
1746 {
1747 // Call even during netplay, so input-recording movies recorded during netplay will play back properly.
1748 MDFNMOV_ProcessInput(PortData, PortDataLen, MDFNGameInfo->PortInfo.size());
1749 }
1750 }
1751
MDFN_MidLineUpdate(EmulateSpecStruct * espec,int y)1752 void MDFN_MidLineUpdate(EmulateSpecStruct *espec, int y)
1753 {
1754 //MDFND_MidLineUpdate(espec, y);
1755 }
1756
MDFNI_Emulate(EmulateSpecStruct * espec)1757 void MDFNI_Emulate(EmulateSpecStruct *espec)
1758 {
1759 #if 0
1760 {
1761 static unsigned osc = 0;
1762 MDFN_PixelFormat nf;
1763
1764 osc = (osc + 1) % 3;
1765
1766 nf.bpp = 16;
1767 nf.colorspace = MDFN_COLORSPACE_RGB;
1768 if(osc == 0)
1769 {
1770 nf.Rshift = 10;
1771 nf.Gshift = 5;
1772 nf.Bshift = 0;
1773 nf.Rprec = 5;
1774 nf.Gprec = 5;
1775 nf.Bprec = 5;
1776 }
1777 else if(osc == 1)
1778 {
1779 nf.Rshift = 11;
1780 nf.Gshift = 5;
1781 nf.Bshift = 0;
1782 nf.Rprec = 5;
1783 nf.Gprec = 6;
1784 nf.Bprec = 5;
1785 }
1786 nf.Ashift = 16;
1787 nf.Aprec = 8;
1788
1789 if(osc == 2)
1790 nf = espec->surface->format;
1791 //
1792 espec->surface->SetFormat(nf, false);
1793 //
1794 MDFN_Notify(MDFN_NOTICE_STATUS, "%2d %d\n", nf.bpp, nf.Gprec);
1795 }
1796 #endif
1797 //
1798 #if 0
1799 {
1800 static const double rates[8] = { 22050, 22222, 44100, 45454, 48000, 64000, 96000, 192000 };
1801 espec->SoundRate = rates[(rand() >> 14) & 0x7];
1802 }
1803 #endif
1804 //
1805 multiplier_save = 1;
1806 volume_save = 1;
1807
1808 if(!espec->CustomPalette)
1809 {
1810 espec->CustomPalette = CustomPalette;
1811 espec->CustomPaletteNumEntries = CustomPaletteNumEntries;
1812 }
1813
1814 // Initialize some espec member data to zero, to catch some types of bugs.
1815 espec->DisplayRect.x = 0;
1816 espec->DisplayRect.w = 0;
1817 espec->DisplayRect.y = 0;
1818 espec->DisplayRect.h = 0;
1819
1820 assert((bool)(espec->SoundBuf != NULL) == (bool)espec->SoundRate && (bool)espec->SoundRate == (bool)espec->SoundBufMaxSize);
1821
1822 espec->SoundBufSize = 0;
1823
1824 if(last_pixel_format != espec->surface->format)
1825 {
1826 espec->VideoFormatChanged = true;
1827
1828 last_pixel_format = espec->surface->format;
1829 }
1830
1831 if(fabs(espec->SoundRate - last_sound_rate) >= 0.5)
1832 {
1833 //puts("Rate Change");
1834 espec->SoundFormatChanged = true;
1835 last_sound_rate = espec->SoundRate;
1836
1837 ff_resampler.buffer_size((espec->SoundRate / 2) * 2);
1838 }
1839
1840 // We want to record movies without any dropped video frames and without fast-forwarding sound distortion and without custom volume.
1841 // The same goes for WAV recording(sans the dropped video frames bit :b).
1842 if(qtrecorder || wavrecorder)
1843 {
1844 multiplier_save = espec->soundmultiplier;
1845 espec->soundmultiplier = 1;
1846
1847 volume_save = espec->SoundVolume;
1848 espec->SoundVolume = 1;
1849 }
1850
1851 if(MDFNGameInfo->TransformInput)
1852 MDFNGameInfo->TransformInput();
1853
1854 Netplay_Update(PortDevice, PortData, PortDataLen);
1855
1856 MDFNMOV_ProcessInput(PortData, PortDataLen, MDFNGameInfo->PortInfo.size());
1857
1858 if(qtrecorder)
1859 espec->skip = 0;
1860
1861 if(TBlur_IsOn())
1862 espec->skip = 0;
1863
1864 if(espec->NeedRewind)
1865 {
1866 if(MDFNnetplay)
1867 {
1868 espec->NeedRewind = false;
1869 MDFN_Notify(MDFN_NOTICE_STATUS, _("Can't rewind during netplay."));
1870 }
1871 }
1872
1873 // Don't even save states with state rewinding if netplay is enabled, it will degrade netplay performance, and can cause
1874 // desynchs with some emulation(IE SNES based on bsnes).
1875
1876 if(MDFNnetplay)
1877 espec->NeedSoundReverse = false;
1878 else
1879 espec->NeedSoundReverse = MDFNSRW_Frame(espec->NeedRewind);
1880
1881 MDFNGameInfo->Emulate(espec);
1882
1883 if(MDFNnetplay)
1884 Netplay_PostProcess(PortDevice, PortData, PortDataLen);
1885
1886 //
1887 // Sanity checks
1888 //
1889 if(!espec->skip || espec->InterlaceOn)
1890 {
1891 if(espec->DisplayRect.h <= 0)
1892 {
1893 fprintf(stderr, "[BUG] espec->DisplayRect.h <= 0: %d\n", espec->DisplayRect.h);
1894 }
1895
1896 if(espec->DisplayRect.y < 0)
1897 {
1898 fprintf(stderr, "[BUG] espec->DisplayRect.y < 0: %d\n", espec->DisplayRect.y);
1899 }
1900
1901 if(espec->LineWidths[0] == ~0)
1902 {
1903 if(espec->DisplayRect.w <= 0)
1904 {
1905 fprintf(stderr, "[BUG] espec->DisplayRect.w <= 0: %d\n", espec->DisplayRect.w);
1906 }
1907 }
1908 else
1909 {
1910 for(int32 y = 0; y < espec->DisplayRect.h; y++)
1911 {
1912 if((y ^ espec->InterlaceField) & espec->InterlaceOn)
1913 continue;
1914
1915 const int32& lw = espec->LineWidths[espec->DisplayRect.y + y];
1916
1917 if(lw <= 0)
1918 fprintf(stderr, "[BUG] espec->LineWidths[%d] <= 0: %d\n", espec->DisplayRect.y + y, lw);
1919 #ifndef MDFN_ENABLE_DEV_BUILD
1920 break; // Only check one line unless this is a dev build, for (very minor) performance reasons.
1921 #endif
1922 }
1923 }
1924 }
1925
1926 if(!espec->MasterCycles)
1927 {
1928 fprintf(stderr, "[BUG] espec->MasterCycles == 0\n");
1929 }
1930
1931 if(espec->MasterCycles < espec->MasterCycles_InternalProcessed)
1932 {
1933 fprintf(stderr, "[BUG] espec->MasterCycles < espec->MasterCycles_InternalProcessed\n");
1934 }
1935
1936 //
1937 //
1938 //
1939
1940 if(espec->InterlaceOn)
1941 {
1942 if(!PrevInterlaced)
1943 deint->ClearState();
1944
1945 deint->Process(espec->surface, espec->DisplayRect, espec->LineWidths, espec->InterlaceField);
1946 PrevInterlaced = true;
1947 }
1948 else
1949 PrevInterlaced = false;
1950
1951 ProcessAudio(espec);
1952
1953 if(qtrecorder)
1954 {
1955 int16 *sb_backup = espec->SoundBuf;
1956 int32 sbs_backup = espec->SoundBufSize;
1957
1958 if(SoundBufPristine.size())
1959 {
1960 espec->SoundBuf = &SoundBufPristine[0];
1961 espec->SoundBufSize = SoundBufPristine.size() / MDFNGameInfo->soundchan;
1962 }
1963
1964 try
1965 {
1966 qtrecorder->WriteFrame(espec->surface, espec->DisplayRect, espec->LineWidths, espec->SoundBuf, espec->SoundBufSize, espec->MasterCycles);
1967 }
1968 catch(std::exception &e)
1969 {
1970 MDFND_OutputNotice(MDFN_NOTICE_ERROR, e.what());
1971 delete qtrecorder;
1972 qtrecorder = NULL;
1973 }
1974
1975 SoundBufPristine.clear();
1976
1977 espec->SoundBuf = sb_backup;
1978 espec->SoundBufSize = sbs_backup;
1979 }
1980
1981 if(TBlur_IsOn())
1982 TBlur_Run(espec);
1983 }
1984
StateAction_RINP(StateMem * sm,const unsigned load,const bool data_only)1985 static void StateAction_RINP(StateMem* sm, const unsigned load, const bool data_only)
1986 {
1987 char namebuf[16][2 + 8 + 1];
1988
1989 if(!data_only)
1990 {
1991 for(unsigned x = 0; x < 16; x++)
1992 {
1993 trio_snprintf(namebuf[x], sizeof(namebuf[x]), "%02x%08x", x, PortDevice[x]);
1994 }
1995 }
1996
1997 #define SFRIH(x) SFPTR8N(PortData[x], PortDataLen[x], namebuf[x])
1998 SFORMAT StateRegs[] =
1999 {
2000 SFRIH(0), SFRIH(1), SFRIH(2), SFRIH(3), SFRIH(4), SFRIH(5), SFRIH(6), SFRIH(7),
2001 SFRIH(8), SFRIH(9), SFRIH(10), SFRIH(11), SFRIH(12), SFRIH(13), SFRIH(14), SFRIH(15),
2002 SFEND
2003 };
2004 #undef SFRIH
2005
2006 MDFNSS_StateAction(sm, load, data_only, StateRegs, "MDFNRINP", true);
2007 }
2008
MDFN_StateAction(StateMem * sm,const unsigned load,const bool data_only)2009 void MDFN_StateAction(StateMem *sm, const unsigned load, const bool data_only)
2010 {
2011 if(DMStatus.size())
2012 {
2013 std::copy(DMStatus.begin(), DMStatus.end(), DMStatusSaveStateTemp.begin());
2014 //
2015 //
2016 SFORMAT StateRegs[] =
2017 {
2018 SFVARN(DMStatusSaveStateTemp.data()->state_idx, DMStatusSaveStateTemp.size(), sizeof(*DMStatusSaveStateTemp.data()), DMStatusSaveStateTemp.data(), "state_idx"),
2019 SFVARN(DMStatusSaveStateTemp.data()->media_idx, DMStatusSaveStateTemp.size(), sizeof(*DMStatusSaveStateTemp.data()), DMStatusSaveStateTemp.data(), "media_idx"),
2020 SFVARN(DMStatusSaveStateTemp.data()->orientation_idx, DMStatusSaveStateTemp.size(), sizeof(*DMStatusSaveStateTemp.data()), DMStatusSaveStateTemp.data(), "orientation_idx"),
2021
2022 SFEND
2023 };
2024
2025 if(MDFNSS_StateAction(sm, load, data_only, StateRegs, "MDFNDRIVE_00000000", true) && load)
2026 {
2027 // Be sure to set media before loading the emulation module state, as setting media
2028 // may affect what state is saved in the emulation module code, and setting media
2029 // can also have side effects(that will be undone by the state load).
2030
2031 if(ValidateDMS(DMStatusSaveStateTemp))
2032 {
2033 //
2034 // Internally(to the emulation core) set all drives to a no-media-present state so the core won't freak
2035 // out by the temporary insertion of the same medium into different drives simultaneously.
2036 //
2037 for(uint32 drive_idx = 0; drive_idx < DMSNoMedia.size(); drive_idx++)
2038 MDFNGameInfo->SetMedia(drive_idx, DMSNoMedia[drive_idx], 0, 0);
2039 //
2040 //
2041 for(uint32 drive_idx = 0; drive_idx < DMStatusSaveStateTemp.size(); drive_idx++)
2042 {
2043 auto const& dmssste = DMStatusSaveStateTemp[drive_idx];
2044 const bool change_notif = dmssste.state_idx != DMStatus[drive_idx].state_idx ||
2045 dmssste.media_idx != DMStatus[drive_idx].media_idx ||
2046 dmssste.orientation_idx != DMStatus[drive_idx].orientation_idx;
2047 //
2048 DMStatus[drive_idx] = dmssste;
2049 //
2050 //
2051 MDFNGameInfo->SetMedia(drive_idx, dmssste.state_idx, dmssste.media_idx, dmssste.orientation_idx);
2052
2053 if(change_notif)
2054 MDFND_MediaSetNotification(drive_idx, dmssste.state_idx, dmssste.media_idx, dmssste.orientation_idx);
2055 }
2056 }
2057 }
2058 }
2059 //
2060 //
2061 //
2062 StateAction_RINP(sm, load, data_only);
2063
2064 if(data_only)
2065 MDFNMOV_StateAction(sm, load);
2066
2067 MDFNGameInfo->StateAction(sm, load, data_only);
2068 }
2069
2070 static int curindent = 0;
2071
MDFN_indent(int indent)2072 void MDFN_indent(int indent)
2073 {
2074 curindent += indent;
2075 if(curindent < 0)
2076 {
2077 fprintf(stderr, "MDFN_indent negative!\n");
2078 curindent = 0;
2079 }
2080 }
2081
2082 static uint8 lastchar = 0;
MDFN_printf(const char * format,...)2083 void MDFN_printf(const char *format, ...) noexcept
2084 {
2085 char *format_temp;
2086 char *temp;
2087 unsigned int x, newlen;
2088
2089 va_list ap;
2090 va_start(ap,format);
2091
2092
2093 // First, determine how large our format_temp buffer needs to be.
2094 uint8 lastchar_backup = lastchar; // Save lastchar!
2095 for(newlen=x=0;x<strlen(format);x++)
2096 {
2097 if(lastchar == '\n' && format[x] != '\n')
2098 {
2099 int y;
2100 for(y=0;y<curindent;y++)
2101 newlen++;
2102 }
2103 newlen++;
2104 lastchar = format[x];
2105 }
2106
2107 format_temp = (char *)malloc(newlen + 1); // Length + NULL character, duh
2108
2109 // Now, construct our format_temp string
2110 lastchar = lastchar_backup; // Restore lastchar
2111 for(newlen=x=0;x<strlen(format);x++)
2112 {
2113 if(lastchar == '\n' && format[x] != '\n')
2114 {
2115 int y;
2116 for(y=0;y<curindent;y++)
2117 format_temp[newlen++] = ' ';
2118 }
2119 format_temp[newlen++] = format[x];
2120 lastchar = format[x];
2121 }
2122
2123 format_temp[newlen] = 0;
2124
2125 temp = trio_vaprintf(format_temp, ap);
2126 free(format_temp);
2127
2128 MDFND_OutputInfo(temp);
2129 free(temp);
2130
2131 va_end(ap);
2132 }
2133
MDFN_Notify(MDFN_NoticeType t,const char * format,...)2134 void MDFN_Notify(MDFN_NoticeType t, const char* format, ...) noexcept
2135 {
2136 char* s;
2137 va_list ap;
2138
2139 va_start(ap, format);
2140
2141 s = trio_vaprintf(format, ap);
2142 if(!s)
2143 {
2144 MDFND_OutputNotice(t, "Error allocating memory for the message!");
2145 }
2146 else
2147 {
2148 MDFND_OutputNotice(t, s);
2149 free(s);
2150 }
2151
2152 va_end(ap);
2153 }
2154
MDFN_DebugPrintReal(const char * file,const int line,const char * format,...)2155 void MDFN_DebugPrintReal(const char *file, const int line, const char *format, ...)
2156 {
2157 char *temp;
2158
2159 va_list ap;
2160
2161 va_start(ap, format);
2162
2163 temp = trio_vaprintf(format, ap);
2164 printf("%s:%d %s\n", file, line, temp);
2165 free(temp);
2166
2167 va_end(ap);
2168 }
2169
MDFN_DoSimpleCommand(int cmd)2170 void MDFN_DoSimpleCommand(int cmd)
2171 {
2172 MDFNGameInfo->DoSimpleCommand(cmd);
2173 }
2174
MDFN_QSimpleCommand(int cmd)2175 void MDFN_QSimpleCommand(int cmd)
2176 {
2177 if(MDFNnetplay)
2178 NetplaySendCommand(cmd, 0);
2179 else
2180 {
2181 if(!MDFNMOV_IsPlaying())
2182 {
2183 MDFN_DoSimpleCommand(cmd);
2184 MDFNMOV_AddCommand(cmd);
2185 }
2186 }
2187 }
2188
MDFNI_Power(void)2189 void MDFNI_Power(void)
2190 {
2191 assert(MDFNGameInfo);
2192
2193 MDFN_QSimpleCommand(MDFN_MSC_POWER);
2194 }
2195
MDFNI_Reset(void)2196 void MDFNI_Reset(void)
2197 {
2198 assert(MDFNGameInfo);
2199
2200 MDFN_QSimpleCommand(MDFN_MSC_RESET);
2201 }
2202
2203 // Arcade-support functions
2204
2205
2206 //
2207 // Quick and dirty kludge until we can (re-)abstract DIP switch handling properly.
2208 //
2209 }
2210
2211 #ifdef WANT_NES_EMU
2212 namespace MDFN_IEN_NES
2213 {
2214 void MDFN_VSUniToggleDIPView(void);
2215 }
2216 #endif
2217
2218 namespace Mednafen
2219 {
MDFNI_ToggleDIPView(void)2220 void MDFNI_ToggleDIPView(void)
2221 {
2222 #ifdef WANT_NES_EMU
2223 if(MDFNGameInfo == &EmulatedNES)
2224 {
2225 MDFN_IEN_NES::MDFN_VSUniToggleDIPView();
2226 }
2227 #endif
2228 }
2229
MDFNI_ToggleDIP(int which)2230 void MDFNI_ToggleDIP(int which)
2231 {
2232 assert(MDFNGameInfo);
2233 assert(which >= 0);
2234
2235 MDFN_QSimpleCommand(MDFN_MSC_TOGGLE_DIP0 + which);
2236 }
2237
MDFNI_InsertCoin(void)2238 void MDFNI_InsertCoin(void)
2239 {
2240 assert(MDFNGameInfo);
2241
2242 MDFN_QSimpleCommand(MDFN_MSC_INSERT_COIN);
2243 }
2244
2245 //
2246 // Disk/Disc-based system support functions
2247 //
2248
ValidateDMS(const std::vector<DriveMediaStatus> & dms)2249 static bool ValidateDMS(const std::vector<DriveMediaStatus>& dms)
2250 {
2251 for(uint32 drive_idx = 0; drive_idx < dms.size(); drive_idx++)
2252 {
2253 const RMD_Drive& drive = MDFNGameInfo->RMD->Drives[drive_idx];
2254 const uint32 state_idx = dms[drive_idx].state_idx;
2255 const uint32 media_idx = dms[drive_idx].media_idx;
2256 const uint32 orientation_idx = dms[drive_idx].orientation_idx;
2257
2258 // Ensure state is valid
2259 if(state_idx >= drive.PossibleStates.size())
2260 {
2261 MDFN_Notify(MDFN_NOTICE_WARNING, _("Denied attempt to put drive into non-existent state(drive_idx=0x%08x, state_idx=0x%08x, media_idx=0x%08x, orientation_idx=0x%08x)."), drive_idx, state_idx, media_idx, orientation_idx);
2262 return false;
2263 }
2264 const RMD_State& state = drive.PossibleStates[state_idx];
2265
2266 // Ensure media(and orientation) is valid
2267 if(state.MediaPresent)
2268 {
2269 if(media_idx >= MDFNGameInfo->RMD->Media.size())
2270 {
2271 MDFN_Notify(MDFN_NOTICE_WARNING, _("Denied attempt to put non-existent medium into drive(drive_idx=0x%08x, state_idx=0x%08x, media_idx=0x%08x, orientation_idx=0x%08x)."), drive_idx, state_idx, media_idx, orientation_idx);
2272 return false;
2273 }
2274
2275 if(orientation_idx && orientation_idx >= MDFNGameInfo->RMD->Media[media_idx].Orientations.size())
2276 {
2277 MDFN_Notify(MDFN_NOTICE_WARNING, _("Denied attempt to put medium with non-existent orientation into drive(drive_idx=0x%08x, state_idx=0x%08x, media_idx=0x%08x, orientation_idx=0x%08x)."), drive_idx, state_idx, media_idx, orientation_idx);
2278 return false;
2279 }
2280 //
2281 //
2282 {
2283 const uint32 media_type = MDFNGameInfo->RMD->Media[media_idx].MediaType;
2284 bool cm_ok = false;
2285
2286 for(auto const& cme : MDFNGameInfo->RMD->Drives[drive_idx].CompatibleMedia)
2287 {
2288 if(cme == media_type)
2289 {
2290 cm_ok = true;
2291 break;
2292 }
2293 }
2294
2295 if(!cm_ok)
2296 {
2297 MDFN_Notify(MDFN_NOTICE_WARNING, _("Denied attempt to put incompatible medium into drive(drive_idx=0x%08x, state_idx=0x%08x, media_idx=0x%08x, orientation_idx=0x%08x)."), drive_idx, state_idx, media_idx, orientation_idx);
2298 return false;
2299 }
2300 }
2301 //
2302 //
2303 for(uint32 check_drive_idx = 0; check_drive_idx < MDFNGameInfo->RMD->Drives.size(); check_drive_idx++)
2304 {
2305 if(check_drive_idx == drive_idx)
2306 continue;
2307 //
2308 const RMD_Drive& check_drive = MDFNGameInfo->RMD->Drives[check_drive_idx];
2309 const RMD_State& check_state = check_drive.PossibleStates[dms[check_drive_idx].state_idx];
2310 const uint32 check_media_idx = dms[check_drive_idx].media_idx;
2311
2312 if(check_state.MediaPresent && media_idx == check_media_idx)
2313 {
2314 MDFN_Notify(MDFN_NOTICE_WARNING, _("Denied attempt to put in-use medium into another drive(drive_idx=0x%08x, state_idx=0x%08x, media_idx=0x%08x, orientation_idx=0x%08x - check_drive_idx=0x%08x)."), drive_idx, state_idx, media_idx, orientation_idx, check_drive_idx);
2315 return false;
2316 }
2317 }
2318 }
2319 }
2320
2321 return true;
2322 }
2323
2324 /* Normal chain:
2325
2326 MDFNI_SetMedia()
2327 NetplaySendCommand()
2328 MDFNMOVAddCommand()
2329 MDFN_UntrustedSetMedia()
2330 ValidateDMS()
2331 MDFNGameInfo->SetMedia()
2332 MDFND_MediaSetNotification()
2333
2334 MDFN_StateAction()
2335 ValidateDMS()
2336 MDFNGameInfo->SetMedia()
2337 MDFND_MediaSetNotification()
2338
2339 MDFN_UntrustedSetMedia() may be called from the movie and netplay code after receiving command data.
2340 */
MDFN_UntrustedSetMedia(uint32 drive_idx,uint32 state_idx,uint32 media_idx,uint32 orientation_idx)2341 bool MDFN_UntrustedSetMedia(uint32 drive_idx, uint32 state_idx, uint32 media_idx, uint32 orientation_idx)
2342 {
2343 //printf("MDFN_UntrustedSetMedia: %d %d %d %d\n", drive_idx, state_idx, media_idx, orientation_idx);
2344
2345 if(!MDFNGameInfo->SetMedia)
2346 return false;
2347
2348 // Ensure drive is valid.
2349 if(drive_idx >= MDFNGameInfo->RMD->Drives.size())
2350 {
2351 MDFN_Notify(MDFN_NOTICE_WARNING, _("Rejected attempt to insert medium into non-existent drive(drive_idx=0x%08x, state_idx=0x%08x, media_idx=0x%08x, orientation_idx=0x%08x)."), drive_idx, state_idx, media_idx, orientation_idx);
2352 return false;
2353 }
2354 //
2355 //
2356 //
2357 assert(drive_idx < DMStatus.size());
2358 //
2359 std::vector<DriveMediaStatus> new_dms = DMStatus;
2360 DriveMediaStatus& dmse = new_dms[drive_idx];
2361
2362 dmse.state_idx = state_idx;
2363 dmse.media_idx = media_idx;
2364 dmse.orientation_idx = orientation_idx;
2365
2366 if(!ValidateDMS(new_dms))
2367 return false;
2368 //
2369 //
2370 //
2371 DMStatus[drive_idx] = dmse;
2372 MDFNGameInfo->SetMedia(drive_idx, dmse.state_idx, dmse.media_idx, dmse.orientation_idx);
2373 MDFND_MediaSetNotification(drive_idx, dmse.state_idx, dmse.media_idx, dmse.orientation_idx);
2374
2375 return true;
2376 }
2377
MDFNI_SetMedia(uint32 drive_idx,uint32 state_idx,uint32 media_idx,uint32 orientation_idx)2378 bool MDFNI_SetMedia(uint32 drive_idx, uint32 state_idx, uint32 media_idx, uint32 orientation_idx)
2379 {
2380 assert(MDFNGameInfo);
2381
2382 if(MDFNnetplay || MDFNMOV_IsRecording())
2383 {
2384 uint8 buf[4 * 4];
2385
2386 MDFN_en32lsb(&buf[0x0], drive_idx);
2387 MDFN_en32lsb(&buf[0x4], state_idx);
2388 MDFN_en32lsb(&buf[0x8], media_idx);
2389 MDFN_en32lsb(&buf[0xC], orientation_idx);
2390
2391 if(MDFNnetplay)
2392 NetplaySendCommand(MDFNNPCMD_SET_MEDIA, sizeof(buf), buf);
2393
2394 if(MDFNMOV_IsRecording())
2395 MDFNMOV_AddCommand(MDFNNPCMD_SET_MEDIA, sizeof(buf), buf);
2396 }
2397
2398 if(!MDFNnetplay && !MDFNMOV_IsPlaying())
2399 return MDFN_UntrustedSetMedia(drive_idx, state_idx, media_idx, orientation_idx);
2400 else
2401 return false;
2402 }
2403
MDFNI_SetLayerEnableMask(uint64 mask)2404 void MDFNI_SetLayerEnableMask(uint64 mask)
2405 {
2406 if(MDFNGameInfo && MDFNGameInfo->SetLayerEnableMask)
2407 {
2408 MDFNGameInfo->SetLayerEnableMask(mask);
2409 }
2410 }
2411
MDFNI_SetInput(const uint32 port,const uint32 type)2412 uint8* MDFNI_SetInput(const uint32 port, const uint32 type)
2413 {
2414 if(MDFNGameInfo)
2415 {
2416 assert(port < 16 && port < MDFNGameInfo->PortInfo.size());
2417 assert(type < MDFNGameInfo->PortInfo[port].DeviceInfo.size());
2418
2419 if(type != PortDevice[port])
2420 {
2421 size_t tmp_len = MDFNGameInfo->PortInfo[port].DeviceInfo[type].IDII.InputByteSize;
2422 uint8* tmp_ptr;
2423
2424 tmp_ptr = (uint8*)malloc(tmp_len ? tmp_len : 1); // Ensure PortData[port], for valid port, is never NULL, for easier handling in regards to stuff like memcpy()
2425 // (which may have "undefined" behavior when a pointer argument is NULL even when length == 0).
2426 memset(tmp_ptr, 0, tmp_len);
2427
2428 if(PortData[port] != NULL)
2429 free(PortData[port]);
2430
2431 PortData[port] = tmp_ptr;
2432 PortDataLen[port] = tmp_len;
2433 PortDevice[port] = type;
2434
2435 MDFNGameInfo->SetInput(port, MDFNGameInfo->PortInfo[port].DeviceInfo[type].ShortName, PortData[port]);
2436 //MDFND_InputSetNotification(port, type, PortData[port]);
2437 }
2438
2439 return PortData[port];
2440 }
2441 else
2442 return(NULL);
2443 }
2444
2445 }
2446