/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Main emulator module where most things are controlled.
*
*
*
* Authors: Sarah Walker,
* Miran Grca,
* Fred N. van Kempen,
*
* Copyright 2008-2020 Sarah Walker.
* Copyright 2016-2020 Miran Grca.
* Copyright 2017-2020 Fred N. van Kempen.
* Copyright 2021 Laci bá'
*/
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef __APPLE__
#include
#include
#ifdef __aarch64__
#include
#endif
#endif
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/config.h>
#include <86box/mem.h>
#include "cpu.h"
#ifdef USE_DYNAREC
# include "codegen_public.h"
#endif
#include "x86_ops.h"
#include <86box/io.h>
#include <86box/rom.h>
#include <86box/dma.h>
#include <86box/pci.h>
#include <86box/pic.h>
#include <86box/timer.h>
#include <86box/device.h>
#include <86box/pit.h>
#include <86box/random.h>
#include <86box/timer.h>
#include <86box/nvr.h>
#include <86box/machine.h>
#include <86box/bugger.h>
#include <86box/postcard.h>
#include <86box/isamem.h>
#include <86box/isartc.h>
#include <86box/lpt.h>
#include <86box/serial.h>
#include <86box/keyboard.h>
#include <86box/mouse.h>
#include <86box/gameport.h>
#include <86box/fdd.h>
#include <86box/fdc.h>
#include <86box/fdc_ext.h>
#include <86box/hdd.h>
#include <86box/hdc.h>
#include <86box/hdc_ide.h>
#include <86box/scsi.h>
#include <86box/scsi_device.h>
#include <86box/cdrom.h>
#include <86box/zip.h>
#include <86box/mo.h>
#include <86box/scsi_disk.h>
#include <86box/cdrom_image.h>
#include <86box/network.h>
#include <86box/sound.h>
#include <86box/midi.h>
#include <86box/snd_speaker.h>
#include <86box/video.h>
#include <86box/ui.h>
#include <86box/plat.h>
#include <86box/plat_midi.h>
#include <86box/version.h>
/* Stuff that used to be globally declared in plat.h but is now extern there
and declared here instead. */
int dopause; /* system is paused */
int doresize; /* screen resize requested */
volatile int is_quit; /* system exit requested */
uint64_t timer_freq;
char emu_version[200]; /* version ID string */
#ifdef MTR_ENABLED
int tracing_on = 0;
#endif
/* Commandline options. */
int dump_on_exit = 0; /* (O) dump regs on exit */
int do_dump_config = 0; /* (O) dump config on load */
int start_in_fullscreen = 0; /* (O) start in fullscreen */
#ifdef _WIN32
int force_debug = 0; /* (O) force debug output */
#endif
#ifdef USE_WX
int video_fps = RENDER_FPS; /* (O) render speed in fps */
#endif
int settings_only = 0; /* (O) show only the settings dialog */
int confirm_exit_cmdl = 1; /* (O) do not ask for confirmation on quit if set to 0 */
#ifdef _WIN32
uint64_t unique_id = 0;
uint64_t source_hwnd = 0;
#endif
char rom_path[1024] = { '\0'}; /* (O) full path to ROMs */
char log_path[1024] = { '\0'}; /* (O) full path of logfile */
char vm_name[1024] = { '\0'}; /* (O) display name of the VM */
/* Configuration values. */
int window_w; /* (C) window size and */
int window_h; /* position info */
int window_x;
int window_y;
int window_remember;
int vid_resize; /* (C) allow resizing */
int invert_display = 0; /* (C) invert the display */
int suppress_overscan = 0; /* (C) suppress overscans */
int scale = 0; /* (C) screen scale factor */
int dpi_scale = 0; /* (C) DPI scaling of the emulated screen */
int vid_api = 0; /* (C) video renderer */
int vid_cga_contrast = 0; /* (C) video */
int video_fullscreen = 0; /* (C) video */
int video_fullscreen_scale = 0; /* (C) video */
int video_fullscreen_first = 0; /* (C) video */
int enable_overscan = 0; /* (C) video */
int force_43 = 0; /* (C) video */
int video_filter_method = 1; /* (C) video */
int video_vsync = 0; /* (C) video */
int video_framerate = -1; /* (C) video */
char video_shader[512] = { '\0' }; /* (C) video */
int serial_enabled[SERIAL_MAX] = {0,0}; /* (C) enable serial ports */
int bugger_enabled = 0; /* (C) enable ISAbugger */
int postcard_enabled = 0; /* (C) enable POST card */
int isamem_type[ISAMEM_MAX] = { 0,0,0,0 }; /* (C) enable ISA mem cards */
int isartc_type = 0; /* (C) enable ISA RTC card */
int gfxcard = 0; /* (C) graphics/video card */
int sound_is_float = 1; /* (C) sound uses FP values */
int GAMEBLASTER = 0; /* (C) sound option */
int GUS = 0; /* (C) sound option */
int SSI2001 = 0; /* (C) sound option */
int voodoo_enabled = 0; /* (C) video option */
uint32_t mem_size = 0; /* (C) memory size */
int cpu_use_dynarec = 0; /* (C) cpu uses/needs Dyna */
int cpu = 0; /* (C) cpu type */
int fpu_type = 0; /* (C) fpu type */
int time_sync = 0; /* (C) enable time sync */
int confirm_reset = 1; /* (C) enable reset confirmation */
int confirm_exit = 1; /* (C) enable exit confirmation */
int confirm_save = 1; /* (C) enable save confirmation */
#ifdef USE_DISCORD
int enable_discord = 0; /* (C) enable Discord integration */
#endif
int enable_crashdump = 0; /* (C) enable crash dump */
/* Statistics. */
extern int mmuflush;
extern int readlnum;
extern int writelnum;
/* emulator % */
int fps;
int framecount;
extern int CPUID;
extern int output;
int atfullspeed;
char exe_path[2048]; /* path (dir) of executable */
char usr_path[1024]; /* path (dir) of user data */
char cfg_path[1024]; /* full path of config file */
FILE *stdlog = NULL; /* file to log output to */
int scrnsz_x = SCREEN_RES_X; /* current screen size, X */
int scrnsz_y = SCREEN_RES_Y; /* current screen size, Y */
int config_changed; /* config has changed */
int title_update;
int framecountx = 0;
int hard_reset_pending = 0;
int unscaled_size_x = SCREEN_RES_X; /* current unscaled size X */
int unscaled_size_y = SCREEN_RES_Y; /* current unscaled size Y */
int efscrnsz_y = SCREEN_RES_Y;
static wchar_t mouse_msg[3][200];
#ifndef RELEASE_BUILD
static char buff[1024];
static int seen = 0;
static int suppr_seen = 1;
#endif
/*
* Log something to the logfile or stdout.
*
* To avoid excessively-large logfiles because some
* module repeatedly logs, we keep track of what is
* being logged, and catch repeating entries.
*/
void
pclog_ex(const char *fmt, va_list ap)
{
#ifndef RELEASE_BUILD
char temp[1024];
if (strcmp(fmt, "") == 0)
return;
if (stdlog == NULL) {
if (log_path[0] != '\0') {
stdlog = plat_fopen(log_path, "w");
if (stdlog == NULL)
stdlog = stdout;
} else
stdlog = stdout;
}
vsprintf(temp, fmt, ap);
if (suppr_seen && ! strcmp(buff, temp))
seen++;
else {
if (suppr_seen && seen)
fprintf(stdlog, "*** %d repeats ***\n", seen);
seen = 0;
strcpy(buff, temp);
fprintf(stdlog, "%s", temp);
}
fflush(stdlog);
#endif
}
void
pclog_toggle_suppr(void)
{
#ifndef RELEASE_BUILD
suppr_seen ^= 1;
#endif
}
/* Log something. We only do this in non-release builds. */
void
pclog(const char *fmt, ...)
{
#ifndef RELEASE_BUILD
va_list ap;
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
#endif
}
/* Log a fatal error, and display a UI message before exiting. */
void
fatal(const char *fmt, ...)
{
char temp[1024];
va_list ap;
char *sp;
va_start(ap, fmt);
if (stdlog == NULL) {
if (log_path[0] != '\0') {
stdlog = plat_fopen(log_path, "w");
if (stdlog == NULL)
stdlog = stdout;
} else
stdlog = stdout;
}
vsprintf(temp, fmt, ap);
fprintf(stdlog, "%s", temp);
fflush(stdlog);
va_end(ap);
nvr_save();
config_save();
#ifdef ENABLE_808X_LOG
dumpregs(1);
#endif
/* Make sure the message does not have a trailing newline. */
if ((sp = strchr(temp, '\n')) != NULL) *sp = '\0';
/* Cleanly terminate all of the emulator's components so as
to avoid things like threads getting stuck. */
do_stop();
ui_msgbox(MBX_ERROR | MBX_FATAL | MBX_ANSI, temp);
fflush(stdlog);
exit(-1);
}
void
fatal_ex(const char *fmt, va_list ap)
{
char temp[1024];
char *sp;
if (stdlog == NULL) {
if (log_path[0] != '\0') {
stdlog = plat_fopen(log_path, "w");
if (stdlog == NULL)
stdlog = stdout;
} else
stdlog = stdout;
}
vsprintf(temp, fmt, ap);
fprintf(stdlog, "%s", temp);
fflush(stdlog);
nvr_save();
config_save();
#ifdef ENABLE_808X_LOG
dumpregs(1);
#endif
/* Make sure the message does not have a trailing newline. */
if ((sp = strchr(temp, '\n')) != NULL) *sp = '\0';
/* Cleanly terminate all of the emulator's components so as
to avoid things like threads getting stuck. */
do_stop();
ui_msgbox(MBX_ERROR | MBX_FATAL | MBX_ANSI, temp);
fflush(stdlog);
}
#ifdef ENABLE_PC_LOG
int pc_do_log = ENABLE_PC_LOG;
static void
pc_log(const char *fmt, ...)
{
va_list ap;
if (pc_do_log) {
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
}
}
#else
#define pc_log(fmt, ...)
#endif
/*
* Perform initial startup of the PC.
*
* This is the platform-indepenent part of the startup,
* where we check commandline arguments and load a
* configuration file.
*/
int
pc_init(int argc, char *argv[])
{
char path[2048], path2[2048];
char *cfg = NULL, *p;
char temp[128];
struct tm *info;
time_t now;
int c, vmrp = 0;
int ng = 0, lvmp = 0;
uint32_t *uid, *shwnd;
uint32_t lang_init = 0;
/* Grab the executable's full path. */
plat_get_exe_name(exe_path, sizeof(exe_path)-1);
p = plat_get_filename(exe_path);
*p = '\0';
/*
* Get the current working directory.
*
* This is normally the directory from where the
* program was run. If we have been started via
* a shortcut (desktop icon), however, the CWD
* could have been set to something else.
*/
plat_getcwd(usr_path, sizeof(usr_path) - 1);
plat_getcwd(rom_path, sizeof(rom_path) - 1);
memset(path, 0x00, sizeof(path));
memset(path2, 0x00, sizeof(path));
for (c=1; ccpu_type >= CPU_286)
pit_set_clock(cpu_s->rspeed);
else
pit_set_clock(14318184.0);
}
void
pc_full_speed(void)
{
if (! atfullspeed) {
pc_log("Set fullspeed - %i %i\n", is386, AT);
pc_speed_changed();
}
atfullspeed = 1;
}
/* Initialize modules, ran once, after pc_init. */
int
pc_init_modules(void)
{
int c, m;
wchar_t temp[512];
char tempc[512];
#ifdef PRINT_MISSING_MACHINES_AND_VIDEO_CARDS
c = m = 0;
while (machine_get_internal_name_ex(c) != NULL) {
m = machine_available(c);
if (!m)
pclog("Missing machine: %s\n", machine_getname_ex(c));
c++;
}
c = m = 0;
while (video_get_internal_name(c) != NULL) {
memset(tempc, 0, sizeof(tempc));
device_get_name(video_card_getdevice(c), 0, tempc);
if ((c > 1) && !(tempc[0]))
break;
m = video_card_available(c);
if (!m)
pclog("Missing video card: %s\n", tempc);
c++;
}
#endif
pc_log("Scanning for ROM images:\n");
c = m = 0;
while (machine_get_internal_name_ex(m) != NULL) {
c += machine_available(m);
m++;
}
if (c == 0) {
/* No usable ROMs found, aborting. */
return(0);
}
pc_log("A total of %d ROM sets have been loaded.\n", c);
/* Load the ROMs for the selected machine. */
if (! machine_available(machine)) {
swprintf(temp, sizeof(temp), plat_get_string(IDS_2063), machine_getname());
c = 0;
machine = -1;
while (machine_get_internal_name_ex(c) != NULL) {
if (machine_available(c)) {
ui_msgbox_header(MBX_INFO, (wchar_t *) IDS_2128, temp);
machine = c;
config_save();
break;
}
c++;
}
if (machine == -1) {
fatal("No available machines\n");
exit(-1);
return(0);
}
}
/* Make sure we have a usable video card. */
if (! video_card_available(gfxcard)) {
memset(tempc, 0, sizeof(tempc));
device_get_name(video_card_getdevice(gfxcard), 0, tempc);
swprintf(temp, sizeof(temp), plat_get_string(IDS_2064), tempc);
c = 0;
while (video_get_internal_name(c) != NULL) {
gfxcard = -1;
if (video_card_available(c)) {
ui_msgbox_header(MBX_INFO, (wchar_t *) IDS_2128, temp);
gfxcard = c;
config_save();
break;
}
c++;
}
if (gfxcard == -1) {
fatal("No available video cards\n");
exit(-1);
return(0);
}
}
atfullspeed = 0;
random_init();
mem_init();
#ifdef USE_DYNAREC
#if defined(__APPLE__) && defined(__aarch64__)
pthread_jit_write_protect_np(0);
#endif
codegen_init();
#if defined(__APPLE__) && defined(__aarch64__)
pthread_jit_write_protect_np(1);
#endif
#endif
keyboard_init();
joystick_init();
video_init();
fdd_init();
sound_init();
hdc_init();
video_reset_close();
return(1);
}
void
pc_send_ca(uint16_t sc)
{
keyboard_input(1, 0x1D); /* Ctrl key pressed */
keyboard_input(1, 0x38); /* Alt key pressed */
keyboard_input(1, sc);
keyboard_input(0, sc);
keyboard_input(0, 0x38); /* Alt key released */
keyboard_input(0, 0x1D); /* Ctrl key released */
}
/* Send the machine a Control-Alt-DEL sequence. */
void
pc_send_cad(void)
{
pc_send_ca(0x153);
}
/* Send the machine a Control-Alt-ESC sequence. */
void
pc_send_cae(void)
{
pc_send_ca(1);
}
void
pc_reset_hard_close(void)
{
ui_sb_set_ready(0);
/* Close all the memory mappings. */
mem_close();
network_timer_stop();
/* Turn off timer processing to avoid potential segmentation faults. */
timer_close();
suppress_overscan = 0;
nvr_save();
nvr_close();
mouse_close();
lpt_devices_close();
device_close_all();
scsi_device_close_all();
midi_close();
cdrom_close();
zip_close();
mo_close();
scsi_disk_close();
closeal();
video_reset_close();
cpu_close();
}
/*
* This is basically the spot where we start up the actual machine,
* by issuing a 'hard reset' to the entire configuration. Order is
* somewhat important here. Functions here should be named _reset
* really, as that is what they do.
*/
void
pc_reset_hard_init(void)
{
/*
* First, we reset the modules that are not part of
* the actual machine, but which support some of the
* modules that are.
*/
/* Reset the general machine support modules. */
io_init();
/* Turn on and (re)initialize timer processing. */
timer_init();
device_init();
sound_reset();
scsi_reset();
scsi_device_init();
/* Initialize the actual machine and its basic modules. */
machine_init();
/* Reset and reconfigure the serial ports. */
serial_standalone_init();
/* Reset and reconfigure the Sound Card layer. */
sound_card_reset();
/* Reset any ISA memory cards. */
isamem_reset();
/* Reset any ISA RTC cards. */
isartc_reset();
fdc_card_init();
fdd_reset();
/*
* Once the machine has been initialized, all that remains
* should be resetting all devices set up for it, to their
* current configurations !
*
* For now, we will call their reset functions here, but
* that will be a call to device_reset_all() later !
*/
/* Reset some basic devices. */
speaker_init();
lpt_devices_init();
shadowbios = 0;
/*
* Reset the mouse, this will attach it to any port needed.
*/
mouse_reset();
/* Reset the Hard Disk Controller module. */
hdc_reset();
/* Reset and reconfigure the SCSI layer. */
scsi_card_init();
cdrom_hard_reset();
zip_hard_reset();
mo_hard_reset();
scsi_disk_hard_reset();
/* Reset and reconfigure the Network Card layer. */
network_reset();
if (joystick_type)
gameport_update_joystick_type();
ui_sb_update_panes();
if (config_changed) {
config_save();
config_changed = 0;
} else
ui_sb_set_ready(1);
/* Needs the status bar... */
if (bugger_enabled)
device_add(&bugger_device);
if (postcard_enabled)
device_add(&postcard_device);
/* Reset the CPU module. */
resetx86();
dma_reset();
pic_reset();
cpu_cache_int_enabled = cpu_cache_ext_enabled = 0;
atfullspeed = 0;
pc_full_speed();
cycles = cycles_main = 0;
update_mouse_msg();
}
void update_mouse_msg()
{
wchar_t wcpufamily[2048], wcpu[2048], wmachine[2048], *wcp;
mbstowcs(wmachine, machine_getname(), strlen(machine_getname())+1);
if (!cpu_override)
mbstowcs(wcpufamily, cpu_f->name, strlen(cpu_f->name)+1);
else
swprintf(wcpufamily, sizeof_w(wcpufamily), L"[U] %hs", cpu_f->name);
wcp = wcschr(wcpufamily, L'(');
if (wcp) /* remove parentheses */
*(wcp - 1) = L'\0';
mbstowcs(wcpu, cpu_s->name, strlen(cpu_s->name)+1);
swprintf(mouse_msg[0], sizeof_w(mouse_msg[0]), L"%ls v%ls - %%i%%%% - %ls - %ls/%ls - %ls",
EMU_NAME_W, EMU_VERSION_FULL_W, wmachine, wcpufamily, wcpu,
plat_get_string(IDS_2077));
swprintf(mouse_msg[1], sizeof_w(mouse_msg[1]), L"%ls v%ls - %%i%%%% - %ls - %ls/%ls - %ls",
EMU_NAME_W, EMU_VERSION_FULL_W, wmachine, wcpufamily, wcpu,
(mouse_get_buttons() > 2) ? plat_get_string(IDS_2078) : plat_get_string(IDS_2079));
swprintf(mouse_msg[2], sizeof_w(mouse_msg[2]), L"%ls v%ls - %%i%%%% - %ls - %ls/%ls",
EMU_NAME_W, EMU_VERSION_FULL_W, wmachine, wcpufamily, wcpu);
}
void
pc_reset_hard(void)
{
hard_reset_pending = 1;
}
void
pc_close(thread_t *ptr)
{
int i;
/* Wait a while so things can shut down. */
plat_delay_ms(200);
/* Claim the video blitter. */
startblit();
/* Terminate the UI thread. */
is_quit = 1;
#if (defined(USE_DYNAREC) && defined(USE_NEW_DYNAREC))
codegen_close();
#endif
nvr_save();
config_save();
plat_mouse_capture(0);
/* Close all the memory mappings. */
mem_close();
network_timer_stop();
/* Turn off timer processing to avoid potential segmentation faults. */
timer_close();
lpt_devices_close();
for (i=0; irspeed / 100);
mouse_process();
joystick_process();
endblit();
/* Done with this frame, update statistics. */
framecount++;
if (++framecountx >= 100) {
framecountx = 0;
frames = 0;
}
if (title_update) {
mouse_msg_idx = (mouse_type == MOUSE_TYPE_NONE) ? 2 : !!mouse_capture;
swprintf(temp, sizeof_w(temp), mouse_msg[mouse_msg_idx], fps);
#ifdef __APPLE__
/* Needed due to modifying the UI on the non-main thread is a big no-no. */
dispatch_async_f(dispatch_get_main_queue(), wcsdup((const wchar_t *) temp), _ui_window_title);
#else
ui_window_title(temp);
#endif
title_update = 0;
}
}
/* Handler for the 1-second timer to refresh the window title. */
void
pc_onesec(void)
{
fps = framecount;
framecount = 0;
title_update = 1;
}
void
set_screen_size(int x, int y)
{
int owsx = scrnsz_x;
int owsy = scrnsz_y;
int temp_overscan_x = overscan_x;
int temp_overscan_y = overscan_y;
double dx, dy, dtx, dty;
/* Make sure we keep usable values. */
#if 0
pc_log("SetScreenSize(%d, %d) resize=%d\n", x, y, vid_resize);
#endif
if (x < 320) x = 320;
if (y < 200) y = 200;
if (x > 2048) x = 2048;
if (y > 2048) y = 2048;
/* Save the new values as "real" (unscaled) resolution. */
unscaled_size_x = x;
efscrnsz_y = y;
if (suppress_overscan)
temp_overscan_x = temp_overscan_y = 0;
if (force_43) {
dx = (double)x;
dtx = (double)temp_overscan_x;
dy = (double)y;
dty = (double)temp_overscan_y;
/* Account for possible overscan. */
if (!(video_is_ega_vga()) && (temp_overscan_y == 16)) {
/* CGA */
dy = (((dx - dtx) / 4.0) * 3.0) + dty;
} else if (!(video_is_ega_vga()) && (temp_overscan_y < 16)) {
/* MDA/Hercules */
dy = (x / 4.0) * 3.0;
} else {
if (enable_overscan) {
/* EGA/(S)VGA with overscan */
dy = (((dx - dtx) / 4.0) * 3.0) + dty;
} else {
/* EGA/(S)VGA without overscan */
dy = (x / 4.0) * 3.0;
}
}
unscaled_size_y = (int)dy;
} else
unscaled_size_y = efscrnsz_y;
switch(scale) {
case 0: /* 50% */
scrnsz_x = (unscaled_size_x>>1);
scrnsz_y = (unscaled_size_y>>1);
break;
case 1: /* 100% */
scrnsz_x = unscaled_size_x;
scrnsz_y = unscaled_size_y;
break;
case 2: /* 150% */
scrnsz_x = ((unscaled_size_x*3)>>1);
scrnsz_y = ((unscaled_size_y*3)>>1);
break;
case 3: /* 200% */
scrnsz_x = (unscaled_size_x<<1);
scrnsz_y = (unscaled_size_y<<1);
break;
}
/* If the resolution has changed, let the main thread handle it. */
if ((owsx != scrnsz_x) || (owsy != scrnsz_y))
doresize = 1;
else
doresize = 0;
}
void
reset_screen_size(void)
{
set_screen_size(unscaled_size_x, efscrnsz_y);
}
void
set_screen_size_natural(void)
{
set_screen_size(unscaled_size_x, unscaled_size_y);
}
int
get_actual_size_x(void)
{
return(unscaled_size_x);
}
int
get_actual_size_y(void)
{
return(efscrnsz_y);
}