-
-
Notifications
You must be signed in to change notification settings - Fork 65
/
dosbox_pure_libretro.cpp
4420 lines (4042 loc) · 196 KB
/
dosbox_pure_libretro.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (C) 2020-2024 Bernhard Schelling
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include "include/dosbox.h"
#include "include/cpu.h"
#include "include/control.h"
#include "include/render.h"
#include "include/keyboard.h"
#include "include/mouse.h"
#include "include/joystick.h"
#include "include/bios_disk.h"
#include "include/callback.h"
#include "include/dbp_serialize.h"
#include "include/dbp_threads.h"
#include "include/dbp_opengl.h"
#include "src/ints/int10.h"
#include "src/dos/drives.h"
#include "keyb2joypad.h"
#include "libretro-common/include/libretro.h"
#include "libretro-common/include/retro_timers.h"
#include <string>
#include <sstream>
// RETROARCH AUDIO/VIDEO
#if defined(GEKKO) || defined(MIYOO) // From RetroArch/config.def.h
#define DBP_DEFAULT_SAMPLERATE 32000.0
#define DBP_DEFAULT_SAMPLERATE_STRING "32000"
#elif defined(_3DS)
#define DBP_DEFAULT_SAMPLERATE 32730.0
#define DBP_DEFAULT_SAMPLERATE_STRING "32730"
#else
#define DBP_DEFAULT_SAMPLERATE 48000.0
#define DBP_DEFAULT_SAMPLERATE_STRING "48000"
#endif
static retro_system_av_info av_info;
// DOSBOX STATE
static enum DBP_State : Bit8u { DBPSTATE_BOOT, DBPSTATE_EXITED, DBPSTATE_SHUTDOWN, DBPSTATE_REBOOT, DBPSTATE_FIRST_FRAME, DBPSTATE_RUNNING } dbp_state;
static enum DBP_SerializeMode : Bit8u { DBPSERIALIZE_DISABLED, DBPSERIALIZE_STATES, DBPSERIALIZE_REWIND } dbp_serializemode;
static enum DBP_Latency : Bit8u { DBP_LATENCY_DEFAULT, DBP_LATENCY_LOW, DBP_LATENCY_VARIABLE } dbp_latency;
static bool dbp_game_running, dbp_pause_events, dbp_paused_midframe, dbp_frame_pending, dbp_force60fps, dbp_biosreboot, dbp_system_cached, dbp_system_scannable, dbp_refresh_memmaps;
static bool dbp_optionsupdatecallback, dbp_last_hideadvanced, dbp_reboot_set64mem, dbp_last_fastforward, dbp_use_network, dbp_had_game_running, dbp_strict_mode, dbp_legacy_save, dbp_swapstereo;
static char dbp_menu_time, dbp_conf_loading, dbp_reboot_machine;
static Bit8u dbp_alphablend_base;
static float dbp_auto_target, dbp_targetrefreshrate;
static Bit32u dbp_lastmenuticks, dbp_framecount, dbp_emu_waiting, dbp_paused_work;
static Semaphore semDoContinue, semDidPause;
static retro_throttle_state dbp_throttle;
static retro_time_t dbp_lastrun;
static std::string dbp_crash_message;
static std::string dbp_content_path;
static std::string dbp_content_name;
static retro_time_t dbp_boot_time;
static size_t dbp_serializesize;
static Bit16s dbp_content_year;
// DOSBOX AUDIO/VIDEO
static Bit8u buffer_active, dbp_overscan;
static bool dbp_doublescan, dbp_padding;
static struct DBP_Buffer { Bit32u* video, width, height, cap, pad_x, pad_y, border_color; float ratio; } dbp_buffers[3];
enum { DBP_MAX_SAMPLES = 4096 }; // twice amount of mixer blocksize (96khz @ 30 fps max)
static int16_t dbp_audio[DBP_MAX_SAMPLES * 2]; // stereo
static double dbp_audio_remain;
static struct retro_hw_render_callback dbp_hw_render;
static void (*dbp_opengl_draw)(const DBP_Buffer& buf);
// DOSBOX DISC MANAGEMENT
struct DBP_Image { std::string path, longpath; bool mounted = false, remount = false, image_disk = false; char drive; int dirlen, dd; };
static std::vector<DBP_Image> dbp_images;
static std::vector<std::string> dbp_osimages, dbp_shellzips;
static StringToPointerHashMap<void> dbp_vdisk_filter;
static unsigned dbp_image_index;
// DOSBOX INPUT
struct DBP_InputBind
{
Bit8u port, device, index, id;
Bit16s evt, meta; union { Bit16s lastval; Bit32u _32bitalign; };
void Update(Bit16s val);
#define PORT_DEVICE_INDEX_ID(b) (*(Bit32u*)&static_cast<const DBP_InputBind&>(b))
};
enum { DBP_MAX_PORTS = 8, DBP_KEYBOARD_PORT, DBP_PORT_MASK = 0x7, DBP_SHIFT_PORT_BIT = 0x80, DBP_NO_PORT = 255, DBP_JOY_ANALOG_RANGE = 0x8000 }; // analog stick range is -0x8000 to 0x8000
static const char* DBP_KBDNAMES[] =
{
"None","1","2","3","4","5","6","7","8","9","0","Q","W","E","R","T","Y","U","I","O","P","A","S","D","F","G","H","J","K","L","Z","X","C","V","B","N","M",
"F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","Esc","Tab","Backspace","Enter","Space","Left-Alt","Right-Alt","Left-Ctrl","Right-Ctrl","Left-Shift","Right-Shift",
"Caps-Lock","Scroll-Lock","Num-Lock","Grave `","Minus -","Equals =","Backslash","Left-Bracket [","Right-Bracket ]","Semicolon ;","Quote '","Period .","Comma ,","Slash /","Backslash \\",
"Print-Screen","Pause","Insert","Home","Page-Up","Delete","End","Page-Down","Left","Up","Down","Right","NP-1","NP-2","NP-3","NP-4","NP-5","NP-6","NP-7","NP-8","NP-9","NP-0",
"NP-Divide /","NP-Multiply *","NP-Minus -","NP-Plus +","NP-Enter","NP-Period .",""
};
struct DBP_WheelItem { Bit8u port, key_count, k[4]; };
static std::vector<DBP_InputBind> dbp_input_binds;
static std::vector<DBP_WheelItem> dbp_wheelitems;
static std::vector<Bit8u> dbp_custom_mapping;
static Bit8u dbp_port_mode[DBP_MAX_PORTS], dbp_binds_changed, dbp_actionwheel_inputs;
static bool dbp_on_screen_keyboard, dbp_analog_buttons;
static char dbp_mouse_input, dbp_auto_mapping_mode;
static Bit16s dbp_bind_mousewheel, dbp_mouse_x, dbp_mouse_y;
static int dbp_joy_analog_deadzone = (int)(0.15f * (float)DBP_JOY_ANALOG_RANGE);
static float dbp_mouse_speed = 1, dbp_mouse_speed_x = 1;
static const Bit8u* dbp_auto_mapping;
static const char *dbp_auto_mapping_names, *dbp_auto_mapping_title;
#define DBP_GET_JOY_ANALOG_VALUE(V) ((V >= -dbp_joy_analog_deadzone && V <= dbp_joy_analog_deadzone) ? 0.0f : \
((float)((V > dbp_joy_analog_deadzone) ? (V - dbp_joy_analog_deadzone) : (V + dbp_joy_analog_deadzone)) / (float)(DBP_JOY_ANALOG_RANGE - dbp_joy_analog_deadzone)))
// DOSBOX EVENTS
enum DBP_Event_Type : Bit8u
{
DBPET_JOY1X, DBPET_JOY1Y, DBPET_JOY2X, DBPET_JOY2Y, DBPET_JOYMX, DBPET_JOYMY, _DBPET_JOY_AXIS_MAX = DBPET_JOYMY,
DBPET_MOUSEMOVE, _DBPET_ACCUMULATABLE_MAX = DBPET_MOUSEMOVE,
DBPET_MOUSEDOWN, DBPET_MOUSEUP,
DBPET_MOUSESETSPEED, DBPET_MOUSERESETSPEED,
DBPET_JOYHATSETBIT, DBPET_JOYHATUNSETBIT,
DBPET_JOY1DOWN, DBPET_JOY1UP,
DBPET_JOY2DOWN, DBPET_JOY2UP,
DBPET_KEYDOWN, DBPET_KEYUP,
DBPET_ONSCREENKEYBOARD, DBPET_ONSCREENKEYBOARDUP,
DBPET_ACTIONWHEEL, DBPET_ACTIONWHEELUP,
DBPET_SHIFTPORT, DBPET_SHIFTPORTUP,
DBPET_AXISMAPPAIR,
DBPET_CHANGEMOUNTS,
DBPET_REFRESHSYSTEM,
#define DBP_IS_RELEASE_EVENT(EVT) ((EVT) >= DBPET_MOUSEUP && !(EVT & 1))
#define DBP_MAPPAIR_MAKE(KEY1,KEY2) (Bit16s)(((KEY1)<<8)|(KEY2))
#define DBP_MAPPAIR_GET(VAL,META) ((VAL) < 0 ? (Bit8u)(((Bit16u)(META))>>8) : (Bit8u)(((Bit16u)(META))&255))
#define DBP_GETKEYDEVNAME(KEY) ((KEY) == KBD_NONE ? NULL : (KEY) < KBD_LAST ? DBPDEV_Keyboard : DBP_SpecialMappings[(KEY)-DBP_SPECIALMAPPINGS_KEY].dev)
#define DBP_GETKEYNAME(KEY) ((KEY) < KBD_LAST ? DBP_KBDNAMES[(KEY)] : DBP_SpecialMappings[(KEY)-DBP_SPECIALMAPPINGS_KEY].name)
_DBPET_MAX
};
//static const char* DBP_Event_Type_Names[] = { "JOY1X", "JOY1Y", "JOY2X", "JOY2Y", "JOYMX", "JOYMY", "MOUSEMOVE", "MOUSEDOWN", "MOUSEUP", "MOUSESETSPEED", "MOUSERESETSPEED", "JOYHATSETBIT", "JOYHATUNSETBIT", "JOY1DOWN", "JOY1UP", "JOY2DOWN", "JOY2UP", "KEYDOWN", "KEYUP", "ONSCREENKEYBOARD", "ONSCREENKEYBOARDUP", "ACTIONWHEEL", "ACTIONWHEELUP", "SHIFTPORT", "SHIFTPORTUP", "AXIS_TO_KEY", "CHANGEMOUNTS", "REFRESHSYSTEM", "MAX" };
static const char *DBPDEV_Keyboard = "Keyboard", *DBPDEV_Mouse = "Mouse", *DBPDEV_Joystick = "Joystick";
static const struct DBP_SpecialMapping { int16_t evt, meta; const char *dev, *name; } DBP_SpecialMappings[] =
{
{ DBPET_JOYMY, -1, DBPDEV_Mouse, "Move Up" }, // 200
{ DBPET_JOYMY, 1, DBPDEV_Mouse, "Move Down" }, // 201
{ DBPET_JOYMX, -1, DBPDEV_Mouse, "Move Left" }, // 202
{ DBPET_JOYMX, 1, DBPDEV_Mouse, "Move Right" }, // 203
{ DBPET_MOUSEDOWN, 0, DBPDEV_Mouse, "Left Click" }, // 204
{ DBPET_MOUSEDOWN, 1, DBPDEV_Mouse, "Right Click" }, // 205
{ DBPET_MOUSEDOWN, 2, DBPDEV_Mouse, "Middle Click" }, // 206
{ DBPET_MOUSESETSPEED, 1, DBPDEV_Mouse, "Speed Up" }, // 207
{ DBPET_MOUSESETSPEED, -1, DBPDEV_Mouse, "Slow Down" }, // 208
{ DBPET_JOY1Y, -1, DBPDEV_Joystick, "Up" }, // 209
{ DBPET_JOY1Y, 1, DBPDEV_Joystick, "Down" }, // 210
{ DBPET_JOY1X, -1, DBPDEV_Joystick, "Left" }, // 211
{ DBPET_JOY1X, 1, DBPDEV_Joystick, "Right" }, // 212
{ DBPET_JOY1DOWN, 0, DBPDEV_Joystick, "Button 1" }, // 213
{ DBPET_JOY1DOWN, 1, DBPDEV_Joystick, "Button 2" }, // 214
{ DBPET_JOY2DOWN, 0, DBPDEV_Joystick, "Button 3" }, // 215
{ DBPET_JOY2DOWN, 1, DBPDEV_Joystick, "Button 4" }, // 216
{ DBPET_JOYHATSETBIT, 8, DBPDEV_Joystick, "Hat Up" }, // 217
{ DBPET_JOYHATSETBIT, 2, DBPDEV_Joystick, "Hat Down" }, // 218
{ DBPET_JOYHATSETBIT, 1, DBPDEV_Joystick, "Hat Left" }, // 219
{ DBPET_JOYHATSETBIT, 4, DBPDEV_Joystick, "Hat Right" }, // 220
{ DBPET_JOY2Y, -1, DBPDEV_Joystick, "Joy 2 Up" }, // 221
{ DBPET_JOY2Y, 1, DBPDEV_Joystick, "Joy 2 Down" }, // 222
{ DBPET_JOY2X, -1, DBPDEV_Joystick, "Joy 2 Left" }, // 223
{ DBPET_JOY2X, 1, DBPDEV_Joystick, "Joy 2 Right" }, // 224
{ DBPET_ONSCREENKEYBOARD, 0, NULL, "On Screen Keyboard" }, // 225
{ DBPET_ACTIONWHEEL, 0, NULL, "Action Wheel" }, // 226
{ DBPET_SHIFTPORT, 0, NULL, "Port #1 while holding" }, // 227
{ DBPET_SHIFTPORT, 1, NULL, "Port #2 while holding" }, // 228
{ DBPET_SHIFTPORT, 2, NULL, "Port #3 while holding" }, // 229
{ DBPET_SHIFTPORT, 3, NULL, "Port #4 while holding" }, // 230
};
#define DBP_SPECIALMAPPING(key) DBP_SpecialMappings[(key)-DBP_SPECIALMAPPINGS_KEY]
enum { DBP_SPECIALMAPPINGS_KEY = 200, DBP_SPECIALMAPPINGS_MAX = 200+(sizeof(DBP_SpecialMappings)/sizeof(DBP_SpecialMappings[0])) };
enum { DBP_SPECIALMAPPINGS_OSK = 225, DBP_SPECIALMAPPINGS_ACTIONWHEEL = 226 };
enum { DBP_EVENT_QUEUE_SIZE = 256, DBP_DOWN_COUNT_MASK = 127, DBP_DOWN_BY_KEYBOARD = 128 };
static struct DBP_Event { DBP_Event_Type type; Bit8u port; int val, val2; } dbp_event_queue[DBP_EVENT_QUEUE_SIZE];
static int dbp_event_queue_write_cursor;
static int dbp_event_queue_read_cursor;
static int dbp_keys_down_count;
static unsigned char dbp_keys_down[KBD_LAST + 21];
static unsigned short dbp_keymap_dos2retro[KBD_LAST];
static unsigned char dbp_keymap_retro2dos[RETROK_LAST];
// DOSBOX PURE OSD INTERCEPT FUNCTIONS
struct DBP_Interceptor
{
virtual void gfx(DBP_Buffer& buf) = 0;
virtual void input() = 0;
virtual void close() = 0;
virtual bool evnt(DBP_Event_Type type, int val, int val2) { return false; }
virtual bool usegfx() { return true; }
};
static DBP_Interceptor *dbp_intercept, *dbp_intercept_next;
static void DBP_SetIntercept(DBP_Interceptor* intercept) { if (!dbp_intercept) dbp_intercept = intercept; dbp_intercept_next = intercept; }
// LIBRETRO CALLBACKS
#ifndef ANDROID
static void retro_fallback_log(enum retro_log_level level, const char *fmt, ...)
{
(void)level;
va_list va;
va_start(va, fmt);
vfprintf(stderr, fmt, va);
va_end(va);
}
#else
extern "C" int __android_log_write(int prio, const char *tag, const char *text);
static void retro_fallback_log(enum retro_log_level level, const char *fmt, ...) { static char buf[8192]; va_list va; va_start(va, fmt); vsprintf(buf, fmt, va); va_end(va); __android_log_write(2, "DBP", buf); }
#endif
extern retro_time_t dbp_cpu_features_get_time_usec(void);
static retro_perf_get_time_usec_t time_cb = dbp_cpu_features_get_time_usec;
static retro_log_printf_t log_cb = retro_fallback_log;
static retro_environment_t environ_cb;
static retro_video_refresh_t video_cb;
static retro_audio_sample_batch_t audio_batch_cb;
static retro_input_poll_t input_poll_cb;
static retro_input_state_t input_state_cb;
// PERF OVERLAY
static enum DBP_Perf : Bit8u { DBP_PERF_NONE, DBP_PERF_SIMPLE, DBP_PERF_DETAILED } dbp_perf;
static Bit32u dbp_perf_uniquedraw, dbp_perf_count, dbp_perf_totaltime;
//#define DBP_ENABLE_WAITSTATS
#ifdef DBP_ENABLE_WAITSTATS
static Bit32u dbp_wait_pause, dbp_wait_finish, dbp_wait_paused, dbp_wait_continue;
#endif
// PERF FPS COUNTERS
//#define DBP_ENABLE_FPS_COUNTERS
#ifdef DBP_ENABLE_FPS_COUNTERS
static Bit32u dbp_lastfpstick, dbp_fpscount_retro, dbp_fpscount_gfxstart, dbp_fpscount_gfxend, dbp_fpscount_event;
#define DBP_FPSCOUNT(DBP_FPSCOUNT_VARNAME) DBP_FPSCOUNT_VARNAME++;
#else
#define DBP_FPSCOUNT(DBP_FPSCOUNT_VARNAME)
#endif
void retro_notify(int duration, retro_log_level lvl, char const* format,...)
{
static char buf[1024];
va_list ap;
va_start(ap, format);
vsnprintf(buf, sizeof(buf), format, ap);
va_end(ap);
retro_message_ext msg;
msg.msg = buf;
msg.duration = (duration ? (unsigned)abs(duration) : (lvl == RETRO_LOG_ERROR ? 10000 : 4000));
msg.priority = 0;
msg.level = lvl;
msg.target = (duration < 0 ? RETRO_MESSAGE_TARGET_OSD : RETRO_MESSAGE_TARGET_ALL);
msg.type = (duration < 0 ? RETRO_MESSAGE_TYPE_STATUS : RETRO_MESSAGE_TYPE_NOTIFICATION);
if (!environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, &msg) && duration >= 0) log_cb(RETRO_LOG_ERROR, "%s", buf);
}
static const char* retro_get_variable(const char* key, const char* default_value)
{
retro_variable var = { key };
return (environ_cb && environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value ? var.value : default_value);
}
static void retro_set_visibility(const char* key, bool visible)
{
retro_core_option_display disp = { key, visible };
if (environ_cb) environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY, &disp);
}
// ------------------------------------------------------------------------------
void DBP_DOSBOX_ForceShutdown(const Bitu = 0);
void DBP_CPU_ModifyCycles(const char* val, const char* params = NULL);
void DBP_KEYBOARD_ReleaseKeys();
void DBP_CGA_SetModelAndComposite(bool new_model, Bitu new_comp_mode);
void DBP_Hercules_SetPalette(Bit8u pal);
void DBP_SetMountSwappingRequested();
Bit32u DBP_MIXER_GetFrequency();
Bit32u DBP_MIXER_DoneSamplesCount();
void DBP_MIXER_ScrapAudio();
void MIXER_CallBack(void *userdata, uint8_t *stream, int len);
bool MSCDEX_HasDrive(char driveLetter);
int MSCDEX_AddDrive(char driveLetter, const char* physicalPath, Bit8u& subUnit);
int MSCDEX_RemoveDrive(char driveLetter);
void IDE_RefreshCDROMs();
void IDE_SetupControllers(char force_cd_drive_letter = 0);
void NET_SetupEthernet();
bool MIDI_TSF_SwitchSF(const char*);
const char* DBP_MIDI_StartupError(Section* midisec, const char*& arg);
static void DBP_QueueEvent(DBP_Event_Type type, Bit8u port, int val = 0, int val2 = 0)
{
unsigned char* downs = dbp_keys_down;
switch (type)
{
case DBPET_KEYDOWN: DBP_ASSERT(val > KBD_NONE && val < KBD_LAST); goto check_down;
case DBPET_KEYUP: DBP_ASSERT(val > KBD_NONE && val < KBD_LAST); goto check_up;
case DBPET_MOUSEDOWN: DBP_ASSERT(val >= 0 && val < 3); downs += KBD_LAST + 0; goto check_down;
case DBPET_MOUSEUP: DBP_ASSERT(val >= 0 && val < 3); downs += KBD_LAST + 0; goto check_up;
case DBPET_JOY1DOWN: DBP_ASSERT(val >= 0 && val < 2); downs += KBD_LAST + 3; goto check_down;
case DBPET_JOY1UP: DBP_ASSERT(val >= 0 && val < 2); downs += KBD_LAST + 3; goto check_up;
case DBPET_JOY2DOWN: DBP_ASSERT(val >= 0 && val < 2); downs += KBD_LAST + 5; goto check_down;
case DBPET_JOY2UP: DBP_ASSERT(val >= 0 && val < 2); downs += KBD_LAST + 5; goto check_up;
case DBPET_JOYHATSETBIT: DBP_ASSERT(val >= 0 && val < 8); downs += KBD_LAST + 7; goto check_down;
case DBPET_JOYHATUNSETBIT: DBP_ASSERT(val >= 0 && val < 8); downs += KBD_LAST + 7; goto check_up;
case DBPET_ONSCREENKEYBOARD: DBP_ASSERT(val >= 0 && val < 1); downs += KBD_LAST + 15; goto check_down;
case DBPET_ONSCREENKEYBOARDUP: DBP_ASSERT(val >= 0 && val < 1); downs += KBD_LAST + 15; goto check_up;
case DBPET_ACTIONWHEEL: DBP_ASSERT(val >= 0 && val < 1); downs += KBD_LAST + 16; goto check_down;
case DBPET_ACTIONWHEELUP: DBP_ASSERT(val >= 0 && val < 1); downs += KBD_LAST + 16; goto check_up;
case DBPET_SHIFTPORT: DBP_ASSERT(val >= 0 && val < 4); downs += KBD_LAST + 17; goto check_down;
case DBPET_SHIFTPORTUP: DBP_ASSERT(val >= 0 && val < 4); downs += KBD_LAST + 17; goto check_up;
check_down:
if (((++downs[val]) & DBP_DOWN_COUNT_MASK) > 1) return;
if (downs == dbp_keys_down) dbp_keys_down_count++;
break;
check_up:
if (((downs[val]) & DBP_DOWN_COUNT_MASK) == 0 || ((--downs[val]) & DBP_DOWN_COUNT_MASK) > 0) return;
if (downs == dbp_keys_down) dbp_keys_down_count--;
break;
case DBPET_JOY1X: case DBPET_JOY1Y: case DBPET_JOY2X: case DBPET_JOY2Y: case DBPET_JOYMX: case DBPET_JOYMY:
if (val || dbp_intercept) break;
for (const DBP_InputBind& b : dbp_input_binds) // check if another bind is currently influencing the same axis
{
if (!b.lastval) continue;
if (b.evt <= _DBPET_JOY_AXIS_MAX)
{
if ((DBP_Event_Type)b.evt != type) continue;
val = (b.meta ? (b.lastval ? 32767 : 0) * b.meta : b.lastval);
goto found_axis_value;
}
else if (b.device != RETRO_DEVICE_ANALOG) continue;
else for (Bit16s dir = 1; dir >= -1; dir -= 2)
{
Bit16s map = DBP_MAPPAIR_GET(dir, b.meta), dirbval = b.lastval * dir;
if (map < DBP_SPECIALMAPPINGS_KEY || dirbval < 0 || (DBP_Event_Type)DBP_SPECIALMAPPING(map).evt != type) continue;
val = (dirbval < 0 ? 0 : dirbval) * DBP_SPECIALMAPPING(map).meta;
goto found_axis_value;
}
}
found_axis_value:break;
default:;
}
DBP_Event evt = { type, port, val, val2 };
DBP_ASSERT(evt.type != DBPET_AXISMAPPAIR);
int cur = dbp_event_queue_write_cursor, next = ((cur + 1) % DBP_EVENT_QUEUE_SIZE);
if (next == dbp_event_queue_read_cursor)
{
// queue full, thread is probably busy (decompression?), try to collapse a duplicated event
dbp_event_queue_write_cursor = next; //stop event processing
for (int i = cur; (i = ((i + DBP_EVENT_QUEUE_SIZE - 1) % DBP_EVENT_QUEUE_SIZE)) != cur;)
{
DBP_Event ie = dbp_event_queue[i];
for (int j = i; j != cur; j = ((j + DBP_EVENT_QUEUE_SIZE - 1) % DBP_EVENT_QUEUE_SIZE))
{
DBP_Event je = (j == i ? evt : dbp_event_queue[j]);
if (je.type != ie.type) continue;
if (ie.type <= _DBPET_ACCUMULATABLE_MAX) { ie.val += je.val; ie.val2 += je.val2; }
cur = j;
goto remove_element_at_cur;
}
}
// Found nothing to remove, just blindly remove the last element
DBP_ASSERT(false);
//if (1)
//{
// for (next = cur; (cur = ((cur + DBP_EVENT_QUEUE_SIZE - 1) % DBP_EVENT_QUEUE_SIZE)) != next;)
// {
// fprintf(stderr, "EVT [%3d] - %20s (%2d) - %d\n", cur, DBP_Event_Type_Names[dbp_event_queue[cur].type], dbp_event_queue[cur].type, dbp_event_queue[cur].val);
// }
// fprintf(stderr, "EVT [ADD] - %20s (%2d) - %d\n", DBP_Event_Type_Names[evt.type], evt.type, evt.val);
//}
// remove element at cur and shift everything up to next one down
remove_element_at_cur:
next = ((next + DBP_EVENT_QUEUE_SIZE - 1) % DBP_EVENT_QUEUE_SIZE);
for (int n = cur; (n = ((n + 1) % DBP_EVENT_QUEUE_SIZE)) != next; cur = n)
dbp_event_queue[cur] = dbp_event_queue[n];
}
dbp_event_queue[cur] = evt;
dbp_event_queue_write_cursor = next;
}
static void DBP_ReleaseKeyEvents(bool onlyPhysicalKeys)
{
for (Bit8u i = KBD_NONE + 1, iEnd = (onlyPhysicalKeys ? KBD_LAST : KBD_LAST + 21); i != iEnd; i++)
{
if (!dbp_keys_down[i] || (onlyPhysicalKeys && (!(dbp_keys_down[i] & DBP_DOWN_BY_KEYBOARD) || input_state_cb(0, RETRO_DEVICE_KEYBOARD, 0, dbp_keymap_dos2retro[i])))) continue;
dbp_keys_down[i] = 1;
DBP_Event_Type type; int val = i;
if (i < KBD_LAST + 0) type = DBPET_KEYUP;
else if (i < KBD_LAST + 3) { val -= KBD_LAST + 0; type = DBPET_MOUSEUP; }
else if (i < KBD_LAST + 5) { val -= KBD_LAST + 3; type = DBPET_JOY1UP; }
else if (i < KBD_LAST + 7) { val -= KBD_LAST + 5; type = DBPET_JOY2UP; }
else if (i < KBD_LAST + 15) { val -= KBD_LAST + 7; type = DBPET_JOYHATUNSETBIT; }
else if (i < KBD_LAST + 16) { val -= KBD_LAST + 15; type = DBPET_ONSCREENKEYBOARDUP; }
else if (i < KBD_LAST + 17) { val -= KBD_LAST + 16; type = DBPET_ACTIONWHEELUP; }
else { val -= KBD_LAST + 17; type = DBPET_SHIFTPORTUP; }
DBP_QueueEvent(type, DBP_NO_PORT, val);
}
}
void DBP_InputBind::Update(Bit16s val)
{
Bit16s prevval = lastval;
lastval = val; // set before calling DBP_QueueEvent
if (evt <= _DBPET_JOY_AXIS_MAX)
{
// handle analog axis mapped to analog functions
if (device == RETRO_DEVICE_JOYPAD && (!dbp_analog_buttons || device != RETRO_DEVICE_JOYPAD || evt > _DBPET_JOY_AXIS_MAX)) { lastval = prevval; return; } // handled by dbp_analog_buttons
DBP_ASSERT(device == RETRO_DEVICE_JOYPAD || meta == 0); // analog axis mapped to analog functions should always have 0 in meta
DBP_ASSERT(device != RETRO_DEVICE_JOYPAD || meta == 1 || meta == -1); // buttons mapped to analog functions should always have 1 or -1 in meta
DBP_QueueEvent((DBP_Event_Type)evt, port, (meta ? val * meta : val), 0);
}
else if (device != RETRO_DEVICE_ANALOG)
{
// if button is pressed, send the _DOWN, otherwise send _UP
DBP_QueueEvent((DBP_Event_Type)(val ? evt : evt + 1), port, meta);
}
else for (Bit16s dir = 1; dir >= -1; dir -= 2)
{
DBP_ASSERT(evt == DBPET_AXISMAPPAIR);
Bit16s map = DBP_MAPPAIR_GET(dir, meta), dirval = val * dir, dirprevval = prevval * dir;
if (map == KBD_NONE) continue;
if (map < KBD_LAST)
{
if (dirval >= 12000 && dirprevval < 12000) DBP_QueueEvent(DBPET_KEYDOWN, port, map);
if (dirval < 12000 && dirprevval >= 12000) DBP_QueueEvent(DBPET_KEYUP, port, map);
continue;
}
if (map < DBP_SPECIALMAPPINGS_KEY) { DBP_ASSERT(false); continue; }
if (dirval <= 0 && dirprevval <= 0) continue;
const DBP_SpecialMapping& sm = DBP_SPECIALMAPPING(map);
if (sm.evt <= _DBPET_JOY_AXIS_MAX) DBP_QueueEvent((DBP_Event_Type)sm.evt, port, (dirval < 0 ? 0 : dirval) * sm.meta);
else if (dirval >= 12000 && dirprevval < 12000) DBP_QueueEvent((DBP_Event_Type)(sm.evt ), port, sm.meta);
else if (dirval < 12000 && dirprevval >= 12000) DBP_QueueEvent((DBP_Event_Type)(sm.evt + 1), port, sm.meta);
}
}
static void DBP_ReportCoreMemoryMaps()
{
extern const char* RunningProgram;
const bool booted_os = !strcmp(RunningProgram, "BOOT");
const size_t conventional_end = 640 * 1024, memtotal = (MEM_TotalPages() * 4096);
// Give access to entire memory to frontend (cheat and achievements support)
// Instead of raw [OS] [GAME] [EXPANDED MEMORY] we switch the order to be
// [GAME] [OS] [EXPANDED MEMORY] so regardless of the size of the OS environment
// the game memory (below 640k) is always at the same (virtual) address.
struct retro_memory_descriptor mdescs[3] = { 0 }, *mdesc_expandedmem;
if (!booted_os)
{
Bit16u seg_prog_start = (DOS_MEM_START + 2 + 5); // see mcb_sizes in DOS_SetupMemory
while (DOS_MCB(seg_prog_start).GetPSPSeg() == 0x40) // tempmcb2 from DOS_SetupMemory and "memlimit" dos config
seg_prog_start += (Bit16u)(1 + DOS_MCB(seg_prog_start).GetSize()); // skip past fake loadfix segment blocks
const size_t prog_start = PhysMake(seg_prog_start, 0);
mdescs[0].flags = RETRO_MEMDESC_SYSTEM_RAM;
mdescs[0].start = 0;
mdescs[0].len = (conventional_end - prog_start);
mdescs[0].ptr = MemBase + prog_start;
mdescs[1].flags = RETRO_MEMDESC_SYSTEM_RAM;
mdescs[1].start = 0x00100000;
mdescs[1].len = prog_start;
mdescs[1].ptr = MemBase;
mdesc_expandedmem = &mdescs[2];
}
else
{
mdescs[0].flags = RETRO_MEMDESC_SYSTEM_RAM;
mdescs[0].start = 0x00000000;
mdescs[0].len = conventional_end;
mdescs[0].ptr = MemBase;
mdesc_expandedmem = &mdescs[1];
}
mdesc_expandedmem->flags = RETRO_MEMDESC_SYSTEM_RAM;
mdesc_expandedmem->start = 0x00200000;
mdesc_expandedmem->len = memtotal - conventional_end;
mdesc_expandedmem->ptr = MemBase + conventional_end;
#ifndef NDEBUG
log_cb(RETRO_LOG_INFO, "[DOSBOX STATUS] ReportCoreMemoryMaps - Program: %s - Booted OS: %d - Program Memory: %d KB\n", RunningProgram, (int)booted_os, (mdescs[0].len / 1024));
#endif
struct retro_memory_map mmaps = { mdescs, (unsigned)(!booted_os ? 3 : 2) };
environ_cb(RETRO_ENVIRONMENT_SET_MEMORY_MAPS, &mmaps);
dbp_refresh_memmaps = false;
}
enum DBP_ThreadCtlMode { TCM_PAUSE_FRAME, TCM_ON_PAUSE_FRAME, TCM_RESUME_FRAME, TCM_FINISH_FRAME, TCM_ON_FINISH_FRAME, TCM_NEXT_FRAME, TCM_SHUTDOWN, TCM_ON_SHUTDOWN };
static void DBP_ThreadControl(DBP_ThreadCtlMode m)
{
static retro_time_t pausedTimeStart; retro_time_t emuWaitTimeStart;
//#define TCMLOG(x, y)// printf("[%10u] [THREAD CONTROL] %20s %25s - STATE: %d - PENDING: %d - PAUSEEVT: %d - MIDFRAME: %d\n", (unsigned)(time_cb() - dbp_boot_time), x, y, (int)dbp_state, dbp_frame_pending, dbp_pause_events, dbp_paused_midframe);
DBP_ASSERT(dbp_state != DBPSTATE_BOOT && dbp_state != DBPSTATE_SHUTDOWN);
switch (m)
{
case TCM_PAUSE_FRAME:
if (!dbp_frame_pending || dbp_pause_events) goto case_TCM_EMULATION_PAUSED;
dbp_pause_events = true;
#ifdef DBP_ENABLE_WAITSTATS
{ retro_time_t t = time_cb(); semDidPause.Wait(); dbp_wait_pause += (Bit32u)(time_cb() - t); }
#else
semDidPause.Wait();
#endif
dbp_pause_events = dbp_frame_pending = dbp_paused_midframe;
goto case_TCM_EMULATION_PAUSED;
case TCM_ON_PAUSE_FRAME:
DBP_ASSERT(dbp_pause_events && !dbp_paused_midframe);
dbp_paused_midframe = true;
semDidPause.Post();
emuWaitTimeStart = time_cb();
semDoContinue.Wait();
dbp_emu_waiting += (Bit32u)(time_cb() - emuWaitTimeStart);
#ifdef DBP_ENABLE_WAITSTATS
dbp_wait_paused += (Bit32u)(time_cb() - emuWaitTimeStart);
#endif
dbp_paused_midframe = false;
return;
case TCM_RESUME_FRAME:
if (!dbp_frame_pending) return;
DBP_ASSERT(dbp_pause_events);
dbp_pause_events = false;
goto case_TCM_EMULATION_CONTINUES;
case TCM_FINISH_FRAME:
if (!dbp_frame_pending) goto case_TCM_EMULATION_PAUSED;
if (dbp_pause_events) DBP_ThreadControl(TCM_RESUME_FRAME);
#ifdef DBP_ENABLE_WAITSTATS
{ retro_time_t t = time_cb(); semDidPause.Wait(); dbp_wait_finish += (Bit32u)(time_cb() - t); }
#else
semDidPause.Wait();
#endif
DBP_ASSERT(!dbp_paused_midframe);
dbp_frame_pending = false;
goto case_TCM_EMULATION_PAUSED;
case TCM_ON_FINISH_FRAME:
semDidPause.Post();
emuWaitTimeStart = time_cb();
semDoContinue.Wait();
dbp_emu_waiting += (Bit32u)(time_cb() - emuWaitTimeStart);
#ifdef DBP_ENABLE_WAITSTATS
dbp_wait_continue += (Bit32u)(time_cb() - emuWaitTimeStart);
#endif
return;
case TCM_NEXT_FRAME:
DBP_ASSERT(!dbp_frame_pending);
if (dbp_state == DBPSTATE_EXITED) return;
dbp_frame_pending = true;
goto case_TCM_EMULATION_CONTINUES;
case TCM_SHUTDOWN:
if (dbp_frame_pending)
{
dbp_pause_events = true;
semDidPause.Wait();
dbp_pause_events = dbp_frame_pending = false;
}
if (dbp_state == DBPSTATE_EXITED) return;
DBP_DOSBOX_ForceShutdown();
do
{
semDoContinue.Post();
semDidPause.Wait();
} while (dbp_state != DBPSTATE_EXITED);
return;
case TCM_ON_SHUTDOWN:
dbp_state = DBPSTATE_EXITED;
semDidPause.Post();
return;
case_TCM_EMULATION_PAUSED:
if (!pausedTimeStart) pausedTimeStart = time_cb();
if (dbp_refresh_memmaps) DBP_ReportCoreMemoryMaps();
return;
case_TCM_EMULATION_CONTINUES:
if (pausedTimeStart) { dbp_paused_work += (Bit32u)(time_cb() - pausedTimeStart); pausedTimeStart = 0; }
if (dbp_serializesize && dbp_serializemode != DBPSERIALIZE_REWIND) dbp_serializesize = 0;
semDoContinue.Post();
return;
}
}
static inline Bit32s DBP_CyclesForYear(int year, int year_max = 0x7FFFFFF)
{
static const Bit32s Cycles1982to1999[1+1999-1982] = { 900, 1500, 2100, 2750, 3800, 4800, 6300, 7800, 14000, 23800, 27000, 44000, 55000, 66800, 93000, 125000, 200000, 350000 };
return (year > year_max ? DBP_CyclesForYear(year_max, year_max) :
(year < 1982 ? 315 : // Very early 8086/8088 CPU
(year > 1999 ? (500000 + ((year - 2000) * 200000)) : // Pentium III, 600 MHz and later
Cycles1982to1999[year - 1982]))); // Matching speed for year
}
static void DBP_SetCyclesByYear(int year, int year_max)
{
DBP_ASSERT(year > 1970);
CPU_CycleMax = DBP_CyclesForYear(year, year_max);
// Also switch to dynamic core for newer real mode games
if (year >= 1990 && (CPU_AutoDetermineMode & CPU_AUTODETERMINE_CORE))
{
#if (C_DYNAMIC_X86)
if (cpudecoder != CPU_Core_Dyn_X86_Run) { void CPU_Core_Dyn_X86_Cache_Init(bool); CPU_Core_Dyn_X86_Cache_Init(true); cpudecoder = CPU_Core_Dyn_X86_Run; }
#elif (C_DYNREC)
if (cpudecoder != CPU_Core_Dynrec_Run) { void CPU_Core_Dynrec_Cache_Init(bool); CPU_Core_Dynrec_Cache_Init(true); cpudecoder = CPU_Core_Dynrec_Run; }
#endif
}
}
void DBP_SetRealModeCycles()
{
if (cpu.pmode || CPU_CycleAutoAdjust || !(CPU_AutoDetermineMode & CPU_AUTODETERMINE_CYCLES) || !dbp_game_running || dbp_content_year <= 1970) return;
const int year = (machine != MCH_PCJR ? dbp_content_year : 1981);
DBP_SetCyclesByYear(year, 1996);
// When auto switching to a high-speed CPU, enable auto adjust so low spec hardware is allowed to throttle down
if (year >= 1995)
CPU_CycleAutoAdjust = true;
}
static bool DBP_NeedFrameSkip(bool in_emulation)
{
if ((in_emulation ? (dbp_throttle.rate > render.src.fps - 1) : (render.src.fps > dbp_throttle.rate - 1))
|| dbp_throttle.rate < 10 || dbp_latency == DBP_LATENCY_VARIABLE
|| dbp_throttle.mode == RETRO_THROTTLE_FRAME_STEPPING || dbp_throttle.mode == RETRO_THROTTLE_FAST_FORWARD
|| dbp_throttle.mode == RETRO_THROTTLE_SLOW_MOTION || dbp_throttle.mode == RETRO_THROTTLE_REWINDING) return false;
static float accum;
accum += (in_emulation ? (render.src.fps - dbp_throttle.rate) : (dbp_throttle.rate - render.src.fps));
if (accum < dbp_throttle.rate) return false;
//log_cb(RETRO_LOG_INFO, "%s AT %u\n", (in_emulation ? "[GFX_EndUpdate] EMULATING TWO FRAMES" : "[retro_run] SKIP EMULATING FRAME"), dbp_framecount);
accum -= dbp_throttle.rate;
return true;
}
bool DBP_Image_IsCD(const DBP_Image& image)
{
const char* ext = (image.path.size() > 3 ? &*(image.path.end()-3) : NULL);
return (ext && !((ext[1]|0x20) == 'm' || (ext[0]|0x20) == 'v'));
}
const char* DBP_Image_Label(const DBP_Image& image)
{
return (image.longpath.length() ? image.longpath : image.path).c_str() + image.dirlen;
}
static unsigned DBP_AppendImage(const char* in_path, bool sorted)
{
for (DBP_Image& i : dbp_images) if (i.path == in_path) return (unsigned)(&i - &dbp_images[0]); // known
struct Local { static bool GetDriveDepth(DOS_Drive* drv, const char* p, int& res)
{
res++;
for (int n = 0;; n++)
if (DOS_Drive* shadow = drv->GetShadow(n, true)) { if (GetDriveDepth(shadow, p, res)) return true; }
else return (!n && drv->FileExists(p));
}};
int dd = 0;
if (in_path[0] == '$' && Drives[in_path[1]-'A']) Local::GetDriveDepth(Drives[in_path[1]-'A'], in_path + 4, dd);
// insert into image list ordered by drive depth and alphabetically
unsigned insert_index = (unsigned)dbp_images.size();
if (sorted)
for (DBP_Image& i : dbp_images)
if (dd < i.dd || (dd == i.dd && i.path > in_path)) {insert_index = (unsigned)(&i - &dbp_images[0]); break; }
dbp_images.insert(dbp_images.begin() + insert_index, DBP_Image());
DBP_Image& i = dbp_images[insert_index];
i.path = in_path;
i.dd = dd;
for (char longname[256], *path = &i.path[0], *pRoot = (path[0] == '$' && i.path.length() > 4 && Drives[path[1]-'A'] ? path + 4 : NULL), *p = pRoot, *pNext; p; p = pNext)
{
if ((pNext = strchr(p, '\\')) != NULL) *pNext = '\0';
if (Drives[path[1]-'A']->GetLongFileName(i.path.c_str() + 4, longname))
{
if (!i.longpath.length()) i.longpath.append(pRoot, (p - pRoot));
i.longpath.append(longname);
}
else if (i.longpath.length())
{
i.longpath.append(p, ((pNext ? pNext : path + i.path.length()) - p));
}
if (pNext) { *(pNext++) = '\\'; if (i.longpath.length()) i.longpath += '\\'; }
}
const char* labelpath = (i.longpath.length() ? i.longpath : i.path).c_str();
const char *lastSlash = strrchr(labelpath, '/'), *lastBackSlash = strrchr(labelpath, '\\');
i.dirlen = (int)((lastSlash && lastSlash > lastBackSlash ? lastSlash + 1 : (lastBackSlash ? lastBackSlash + 1 : labelpath)) - labelpath);
return insert_index;
}
static bool DBP_ExtractPathInfo(const char* path, const char ** out_path_file = NULL, size_t* out_namelen = NULL, const char ** out_ext = NULL, const char ** out_fragment = NULL, char* out_letter = NULL)
{
if (!path || !*path) return false;
// Skip any slashes at the very end of path, then find the next slash as the start of the loaded file/directory name
const char *path_end = path + strlen(path), *path_file = path_end;
for (bool had_other = false; path_file > path; path_file--)
{
bool is_slash = (path_file[-1] == '/' || path_file[-1] == '\\');
if (is_slash) { if (had_other) break; path_end = path_file - 1; } else had_other = true;
}
const char *ext = strrchr(path_file, '.');
if (ext) ext++; else ext = path_end;
const char *fragment = strrchr(path_file, '#');
if (fragment && ext > fragment) // check if 'FOO.ZIP#BAR.EXE'
{
const char* real_ext = fragment - (fragment - 3 > path_file && fragment[-3] == '.' ? 3 : 4);
if (real_ext > path_file && *real_ext == '.') ext = real_ext+1;
else fragment = NULL;
}
// A drive letter can be specified either by naming the mount file '.<letter>.<extension>' or by loading a path with an added '#<letter>' suffix.
char letter = 0;
const char *p_fra_drive = (fragment && fragment[1] && !fragment[2] ? fragment + 1 : NULL);
const char *p_dot_drive = (ext - path_file > 3 && ext[-3] == '.' && !p_fra_drive ? ext - 2 : NULL);
if (p_fra_drive && (*p_fra_drive >= 'A' && *p_fra_drive <= 'Z')) letter = *p_fra_drive;
else if (p_fra_drive && (*p_fra_drive >= 'a' && *p_fra_drive <= 'z')) letter = *p_fra_drive - 0x20;
else if (p_dot_drive && (*p_dot_drive >= 'A' && *p_dot_drive <= 'Z')) letter = *p_dot_drive;
else if (p_dot_drive && (*p_dot_drive >= 'a' && *p_dot_drive <= 'z')) letter = *p_dot_drive - 0x20;
else p_dot_drive = NULL;
if (out_path_file) *out_path_file = path_file;
if (out_namelen) *out_namelen = (p_dot_drive ? p_dot_drive : ext) - (ext[-1] == '.' ? 1 : 0) - path_file;
if (out_ext) *out_ext = ext;
if (out_fragment) *out_fragment = fragment;
if (out_letter) *out_letter = letter;
return true;
}
static bool DBP_IsMounted(char drive)
{
DBP_ASSERT(drive >= 'A' && drive <= 'Z');
return Drives[drive-'A'] || (drive < 'A'+MAX_DISK_IMAGES && imageDiskList[drive-'A']);
}
static void DBP_Unmount(char drive)
{
DBP_ASSERT(drive >= 'A' && drive <= 'Z');
if (Drives[drive-'A'] && Drives[drive-'A']->UnMount() != 0) { DBP_ASSERT(false); return; }
Drives[drive-'A'] = NULL;
MSCDEX_RemoveDrive(drive);
if (drive < 'A'+MAX_DISK_IMAGES)
if (imageDisk*& dsk = imageDiskList[drive-'A'])
{ delete dsk; dsk = NULL; }
IDE_RefreshCDROMs();
mem_writeb(Real2Phys(dos.tables.mediaid)+(drive-'A')*9,0);
for (DBP_Image& i : dbp_images)
if (i.mounted && i.drive == drive)
i.mounted = false;
}
enum DBP_SaveFileType { SFT_GAMESAVE, SFT_VIRTUALDISK, SFT_DIFFDISK, _SFT_LAST_SAVE_DIRECTORY, SFT_SYSTEMDIR, SFT_NEWOSIMAGE };
static std::string DBP_GetSaveFile(DBP_SaveFileType type, const char** out_filename = NULL, Bit32u* out_diskhash = NULL)
{
std::string res;
const char *env_dir = NULL;
if (environ_cb((type < _SFT_LAST_SAVE_DIRECTORY ? RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY : RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY), &env_dir) && env_dir)
res.assign(env_dir) += CROSS_FILESPLIT;
size_t dir_len = res.size();
if (type < _SFT_LAST_SAVE_DIRECTORY)
{
res.append(dbp_content_name.empty() ? "DOSBox-pure" : dbp_content_name.c_str());
if (type == SFT_GAMESAVE)
{
if (FILE* fSave = fopen_wrap(res.append(".pure.zip").c_str(), "rb")) { fclose(fSave); } // new save exists!
else if (FILE* fSave = fopen_wrap(res.replace(res.length()-8, 3, "sav", 3).c_str(), "rb")) { dbp_legacy_save = true; fclose(fSave); }
else res.replace(res.length()-8, 3, "pur", 3); // use new save
}
else if (type == SFT_VIRTUALDISK)
{
if (!dbp_vdisk_filter.Len())
{
dbp_vdisk_filter.Put("AUTOBOOT.DBP", (void*)(size_t)true);
dbp_vdisk_filter.Put("PADMAP.DBP", (void*)(size_t)true);
dbp_vdisk_filter.Put("DOSBOX~1.CON", (void*)(size_t)true);
dbp_vdisk_filter.Put("DOSBOX.CON", (void*)(size_t)true);
for (const DBP_Image& i : dbp_images) if (i.path[0] == '$' && i.path[1] == 'C')
dbp_vdisk_filter.Put(&i.path[4], (void*)(size_t)true);
}
struct Local { static void FileHash(const char* path, bool, Bit32u size, Bit16u date, Bit16u time, Bit8u attr, Bitu data)
{
size_t pathlen = strlen(path);
if (pathlen >= 2 && path[pathlen-1] == '.' && (path[pathlen-2] == '.' || path[pathlen-2] == '\\')) return; // skip . and ..
if (dbp_vdisk_filter.Get(path)) return;
if (pathlen > 4 && !memcmp(&path[pathlen-4], ".SKC", 4)) { dbp_vdisk_filter.Put(path, (void*)(size_t)true); return; } // remove ZIP seek caches
Bit32u& hash = *(Bit32u*)data;
Bit8u arr[] = { (Bit8u)(size>>24), (Bit8u)(size>>16), (Bit8u)(size>>8), (Bit8u)(size), (Bit8u)(date>>8), (Bit8u)(date), (Bit8u)(time>>8), (Bit8u)(time), attr };
hash = DriveCalculateCRC32(arr, sizeof(arr), DriveCalculateCRC32((const Bit8u*)path, pathlen, hash));
}};
Bit32u hash = (Bit32u)(0x11111111 - 1024) + (Bit32u)atoi(retro_get_variable("dosbox_pure_bootos_dfreespace", "1024"));
DriveFileIterator(Drives['C'-'A'], Local::FileHash, (Bitu)&hash);
res.resize(res.size() + 32);
res.resize(res.size() - 32 + sprintf(&res[res.size() - 32], (hash == 0x11111111 ? ".sav" : "-%08X.sav"), hash));
if (out_diskhash) *out_diskhash = hash;
}
else if (type == SFT_DIFFDISK)
{
res.append("-CDRIVE.sav");
}
}
else if (type == SFT_NEWOSIMAGE)
{
res.append(!dbp_content_name.empty() ? dbp_content_name.c_str() : "Installed OS").append(".img");
size_t num = 1, baselen = res.size() - 4;
while (FILE* f = fopen_wrap(res.c_str(), "rb"))
{
fclose(f);
res.resize(baselen + 16);
res.resize(baselen + sprintf(&res[baselen], " (%d).img", (int)++num));
}
}
if (out_filename) *out_filename = res.c_str() + dir_len;
return std::move(res);
}
static void DBP_SetDriveLabelFromContentPath(DOS_Drive* drive, const char *path, char letter = 'C', const char *path_file = NULL, const char *ext = NULL, bool forceAppendExtension = false)
{
// Use content filename as drive label, cut off at file extension, the first occurence of a ( or [ character or right white space.
if (!path_file && !DBP_ExtractPathInfo(path, &path_file, NULL, &ext)) return;
char lbl[11+1], *lblend = lbl + (ext - path_file > 11 ? 11 : ext - (*ext ? 1 : 0) - path_file);
memcpy(lbl, path_file, lblend - lbl);
for (char* c = lblend; c > lbl; c--) { if (c == lblend || *c == '(' || *c == '[' || (*c <= ' ' && !c[1])) { *c = '\0'; lblend = c; } }
if (forceAppendExtension && ext && ext[0])
{
lblend = (lblend > lbl + 11 - 4 ? lbl + 11 - 4 : lblend);
*lblend = '-';
safe_strncpy(lblend + 1, ext, (lbl+11-lblend));
}
drive->label.SetLabel(lbl, (letter > 'C'), true);
}
static DOS_Drive* DBP_Mount(unsigned image_index = 0, bool unmount_existing = false, char remount_letter = 0, const char* boot = NULL)
{
const char *path = (boot ? boot : dbp_images[image_index].path.c_str()), *path_file, *ext, *fragment; char letter;
if (!DBP_ExtractPathInfo(path, &path_file, NULL, &ext, &fragment, &letter)) return NULL;
if (remount_letter) letter = remount_letter;
std::string path_no_fragment;
if (fragment)
{
path_no_fragment.assign(path, fragment - path);
ext = path_no_fragment.c_str() + (ext - path);
path_file = path_no_fragment.c_str() + (path_file - path);
path = path_no_fragment.c_str();
}
DOS_Drive* drive = NULL;
imageDisk* disk = NULL;
CDROM_Interface* cdrom = NULL;
Bit8u media_byte = 0;
const char* error_type = "content";
if (!strcasecmp(ext, "ZIP") || !strcasecmp(ext, "DOSZ") || !strcasecmp(ext, "DOSC"))
{
if (!letter) letter = (boot ? 'C' : 'D');
if (!unmount_existing && Drives[letter-'A']) return NULL;
std::string* ziperr = NULL;
if ((ext[3]|0x20) != 'c')
drive = zipDrive::MountWithDependencies(path, ziperr, dbp_strict_mode, dbp_legacy_save);
else
{
// When loading a DOSC file, load the corresponding DOSZ file, but strip out a [VARIANT] specifier at the end.
std::string dosz_path(path);
dosz_path.back() = (ext[3] == 'c' ? 'z' : 'Z'); // swap dosc -> dosz with same capitalization
if (const char* dosc_variant = (ext[-2] == ']' ? strrchr(path_file, '[') : NULL))
{
while (dosc_variant > path_file && dosc_variant[-1] == ' ') dosc_variant--;
size_t dosc_variant_len = (ext - 1 - dosc_variant);
dosz_path.erase(dosc_variant - path, dosc_variant_len);
}
drive = zipDrive::MountWithDependencies(dosz_path.c_str(), ziperr, dbp_strict_mode, dbp_legacy_save, path);
}
if (!drive)
{
if (ziperr) { retro_notify(0, RETRO_LOG_ERROR, "%s", ziperr->c_str()); delete ziperr; }
error_type = "ZIP";
goto TRY_DIRECTORY;
}
DBP_SetDriveLabelFromContentPath(drive, path, letter, path_file, ext);
if (boot && letter == 'C') return drive;
}
else if (!strcasecmp(ext, "IMG") || !strcasecmp(ext, "IMA") || !strcasecmp(ext, "VHD") || !strcasecmp(ext, "JRC") || !strcasecmp(ext, "TC"))
{
fatDrive* fat = new fatDrive(path, 512, 0, 0, 0, 0);
if (!fat->loadedDisk || (!fat->created_successfully && letter >= 'A'+MAX_DISK_IMAGES))
{
FAT_TRY_ISO:
delete fat;
goto MOUNT_ISO;
}
else if (!fat->created_successfully)
{
// Check if ISO (based on CDROM_Interface_Image::LoadIsoFile/CDROM_Interface_Image::CanReadPVD)
static const Bit32u pvdoffsets[] = { 32768, 32768+8, 37400, 37400+8, 37648, 37656, 37656+8 };
for (Bit32u pvdoffset : pvdoffsets)
{
Bit8u pvd[8];
if (fat->loadedDisk->Read_Raw(pvd, pvdoffset, 8) == 8 && (!memcmp(pvd, "\1CD001\1", 7) || !memcmp(pvd, "\1CDROM\1", 7)))
goto FAT_TRY_ISO;
}
// Neither FAT nor ISO, just register with BIOS/CMOS for raw sector access and set media table byte
disk = fat->loadedDisk;
fat->loadedDisk = NULL; // don't want it deleted by ~fatDrive
delete fat;
}
else
{
// Copied behavior from IMGMOUNT::Run, force obtaining the label and saving it in label
RealPt save_dta = dos.dta();
dos.dta(dos.tables.tempdta);
DOS_DTA dta(dos.dta());
dta.SetupSearch(255, DOS_ATTR_VOLUME, (char*)"*.*");
fat->FindFirst((char*)"", dta);
dos.dta(save_dta);
drive = fat;
disk = fat->loadedDisk;
if (boot && disk->hardDrive && (!letter || letter == 'C'))
{
// also register with BIOS/CMOS to make this image bootable (make sure it's its own instance as we pass this one to be owned by unionDrive)
fatDrive* fat2 = new fatDrive(path, 512, 0, 0, 0, 0);
imageDiskList['C'-'A'] = fat2->loadedDisk;
fat2->loadedDisk = NULL; // don't want it deleted by ~fatDrive
delete fat2;
return fat;
}
}
if (!letter) letter = (disk->hardDrive ? 'D' : 'A');
media_byte = (disk->hardDrive ? 0xF8 : (disk->active ? disk->GetBiosType() : 0));
}
else if (!strcasecmp(ext, "ISO") || !strcasecmp(ext, "CHD") || !strcasecmp(ext, "CUE") || !strcasecmp(ext, "INS"))
{
MOUNT_ISO:
if (letter < 'D') letter = 'D';
if (!unmount_existing && Drives[letter-'A']) return NULL;
if (DBP_IsMounted(letter)) DBP_Unmount(letter); // needs to be done before constructing isoDrive as it registers itself with MSCDEX overwriting the current drives registration
int error = -1;
isoDrive* iso = new isoDrive(letter, path, 0xF8, error);
if (error)
{
delete iso;
if (DOS_Drive **srcdrv = ((!boot && path[0] == '$' && path[1] >= 'A' && path[1] <= 'Z') ? &Drives[path[1]-'A'] : NULL))
if (DOS_Drive *mirror = dynamic_cast<mirrorDrive*>(*srcdrv))
if (DOS_Drive* shadow = mirror->GetShadow(0, false))
{
*srcdrv = shadow;
drive = DBP_Mount(image_index, unmount_existing, remount_letter);
*srcdrv = mirror;
return drive;
}
error_type = "CD-ROM image";
goto TRY_DIRECTORY;
}
cdrom = iso->GetInterface();
drive = iso;
}
else if (!strcasecmp(ext, "M3U") || !strcasecmp(ext, "M3U8"))
{
FILE* m3u_file_h = fopen_wrap(path, "rb");
if (!m3u_file_h)
{
error_type = "M3U";
goto TRY_DIRECTORY;
}
fseek(m3u_file_h, 0, SEEK_END);
size_t m3u_file_size = ftell(m3u_file_h);
fseek(m3u_file_h, 0, SEEK_SET);
char* m3u = new char[m3u_file_size + 1];
if (!fread(m3u, m3u_file_size, 1, m3u_file_h)) { DBP_ASSERT(0); }
fclose(m3u_file_h);
m3u[m3u_file_size] = '\0';
for (char* p = m3u, *pEnd = p + m3u_file_size; p <= pEnd; p++)
{
if (*p <= ' ') continue;
char* m3u_line = (*p == '#' ? NULL : p);
while (*p != '\0' && *p != '\r' && *p != '\n') p++;
*p = '\0';
if (!m3u_line) continue;
size_t m3u_baselen = (m3u_line[0] == '\\' || m3u_line[0] == '/' || m3u_line[1] == ':' ? 0 : path_file - path);
std::string m3u_path = std::string(path, m3u_baselen) + m3u_line;
DBP_AppendImage(m3u_path.c_str(), false);
}
delete [] m3u;
return NULL;
}
else // path or executable file
{
TRY_DIRECTORY: