/*
* 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.
*
* Platform main support module for Windows.
*
*
*
* Authors: Sarah Walker,
* Miran Grca,
* Fred N. van Kempen,
*
* Copyright 2008-2019 Sarah Walker.
* Copyright 2016-2019 Miran Grca.
* Copyright 2017-2019 Fred N. van Kempen.
*/
#define UNICODE
#define NTDDI_VERSION 0x06010000
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/config.h>
#include <86box/device.h>
#include <86box/keyboard.h>
#include <86box/mouse.h>
#include <86box/timer.h>
#include <86box/nvr.h>
#include <86box/video.h>
#define GLOBAL
#include <86box/plat.h>
#include <86box/plat_midi.h>
#include <86box/ui.h>
#ifdef USE_VNC
# include <86box/vnc.h>
#endif
#include <86box/win_sdl.h>
#include <86box/win_opengl.h>
#include <86box/win.h>
#include <86box/version.h>
#ifdef MTR_ENABLED
#include
#endif
typedef struct {
WCHAR str[512];
} rc_str_t;
/* Platform Public data, specific. */
HINSTANCE hinstance; /* application instance */
HANDLE ghMutex;
LCID lang_id; /* current language ID used */
DWORD dwSubLangID;
int acp_utf8; /* Windows supports UTF-8 codepage */
/* Local data. */
static HANDLE thMain;
static rc_str_t *lpRCstr2048,
*lpRCstr4096,
*lpRCstr4352,
*lpRCstr4608,
*lpRCstr5120,
*lpRCstr5376,
*lpRCstr5632,
*lpRCstr5888,
*lpRCstr6144,
*lpRCstr7168;
static int vid_api_inited = 0;
static char *argbuf;
static int first_use = 1;
static LARGE_INTEGER StartingTime;
static LARGE_INTEGER Frequency;
static const struct {
const char *name;
int local;
int (*init)(void *);
void (*close)(void);
void (*resize)(int x, int y);
int (*pause)(void);
void (*enable)(int enable);
void (*set_fs)(int fs);
void (*reload)(void);
} vid_apis[RENDERERS_NUM] = {
{ "SDL_Software", 1, (int(*)(void*))sdl_inits, sdl_close, NULL, sdl_pause, sdl_enable, sdl_set_fs, NULL },
{ "SDL_Hardware", 1, (int(*)(void*))sdl_inith, sdl_close, NULL, sdl_pause, sdl_enable, sdl_set_fs, NULL },
{ "SDL_OpenGL", 1, (int(*)(void*))sdl_initho, sdl_close, NULL, sdl_pause, sdl_enable, sdl_set_fs, NULL }
#ifdef DEV_BRANCH /* feature-opengl */
,{ "OpenGL_Core", 1, (int(*)(void*))opengl_init, opengl_close, opengl_resize, opengl_pause, NULL, opengl_set_fs, opengl_reload}
#else
,{ "OpenGL_Core", 1, (int(*)(void*))sdl_initho, sdl_close, NULL, sdl_pause, sdl_enable, sdl_set_fs, NULL } /* fall back to SDL_OpenGL */
#endif
#ifdef USE_VNC
,{ "VNC", 0, vnc_init, vnc_close, vnc_resize, vnc_pause, NULL, NULL }
#endif
};
extern int title_update;
#ifdef ENABLE_WIN_LOG
int win_do_log = ENABLE_WIN_LOG;
static void
win_log(const char *fmt, ...)
{
va_list ap;
if (win_do_log) {
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
}
}
#else
#define win_log(fmt, ...)
#endif
static void
LoadCommonStrings(void)
{
int i;
lpRCstr2048 = (rc_str_t *)malloc(STR_NUM_2048*sizeof(rc_str_t));
lpRCstr4096 = (rc_str_t *)malloc(STR_NUM_4096*sizeof(rc_str_t));
lpRCstr4352 = (rc_str_t *)malloc(STR_NUM_4352*sizeof(rc_str_t));
lpRCstr4608 = (rc_str_t *)malloc(STR_NUM_4608*sizeof(rc_str_t));
lpRCstr5120 = (rc_str_t *)malloc(STR_NUM_5120*sizeof(rc_str_t));
lpRCstr5376 = (rc_str_t *)malloc(STR_NUM_5376*sizeof(rc_str_t));
lpRCstr5632 = (rc_str_t *)malloc(STR_NUM_5632*sizeof(rc_str_t));
lpRCstr5888 = (rc_str_t *)malloc(STR_NUM_5888*sizeof(rc_str_t));
lpRCstr6144 = (rc_str_t *)malloc(STR_NUM_6144*sizeof(rc_str_t));
lpRCstr7168 = (rc_str_t *)malloc(STR_NUM_7168*sizeof(rc_str_t));
for (i=0; i 3))
LoadString(hinstance, 5376+i, lpRCstr5376[i].str, 512);
}
for (i=0; i 3))
LoadString(hinstance, 5632+i, lpRCstr5632[i].str, 512);
}
for (i=0; i= 2048) && (i <= 3071))
str = lpRCstr2048[i-2048].str;
else if ((i >= 4096) && (i <= 4351))
str = lpRCstr4096[i-4096].str;
else if ((i >= 4352) && (i <= 4607))
str = lpRCstr4352[i-4352].str;
else if ((i >= 4608) && (i <= 5119))
str = lpRCstr4608[i-4608].str;
else if ((i >= 5120) && (i <= 5375))
str = lpRCstr5120[i-5120].str;
else if ((i >= 5376) && (i <= 5631))
str = lpRCstr5376[i-5376].str;
else if ((i >= 5632) && (i <= 5887))
str = lpRCstr5632[i-5632].str;
else if ((i >= 5888) && (i <= 6143))
str = lpRCstr5888[i-5888].str;
else if ((i >= 6144) && (i <= 7167))
str = lpRCstr6144[i-6144].str;
else
str = lpRCstr7168[i-7168].str;
return((wchar_t *)str);
}
#ifdef MTR_ENABLED
void
init_trace(void)
{
mtr_init("trace.json");
mtr_start();
}
void
shutdown_trace(void)
{
mtr_stop();
mtr_shutdown();
}
#endif
/* Create a console if we don't already have one. */
static void
CreateConsole(int init)
{
HANDLE h;
FILE *fp;
fpos_t p;
int i;
if (! init) {
if (force_debug)
FreeConsole();
return;
}
/* Are we logging to a file? */
p = 0;
(void)fgetpos(stdout, &p);
if (p != -1) return;
/* Not logging to file, attach to console. */
if (! AttachConsole(ATTACH_PARENT_PROCESS)) {
/* Parent has no console, create one. */
if (! AllocConsole()) {
/* Cannot create console, just give up. */
return;
}
}
fp = NULL;
if ((h = GetStdHandle(STD_OUTPUT_HANDLE)) != NULL) {
/* We got the handle, now open a file descriptor. */
if ((i = _open_osfhandle((intptr_t)h, _O_TEXT)) != -1) {
/* We got a file descriptor, now allocate a new stream. */
if ((fp = _fdopen(i, "w")) != NULL) {
/* Got the stream, re-initialize stdout without it. */
(void)freopen("CONOUT$", "w", stdout);
setvbuf(stdout, NULL, _IONBF, 0);
fflush(stdout);
}
}
}
if (fp != NULL) {
fclose(fp);
fp = NULL;
}
}
static void
CloseConsole(void)
{
CreateConsole(0);
}
/* Process the commandline, and create standard argc/argv array. */
static int
ProcessCommandLine(char ***argv)
{
char **args;
int argc_max;
int i, q, argc;
if (acp_utf8) {
i = strlen(GetCommandLineA()) + 1;
argbuf = (char *)malloc(i);
strcpy(argbuf, GetCommandLineA());
} else {
i = c16stombs(NULL, GetCommandLineW(), 0) + 1;
argbuf = (char *)malloc(i);
c16stombs(argbuf, GetCommandLineW(), i);
}
argc = 0;
argc_max = 64;
args = (char **)malloc(sizeof(char *) * argc_max);
if (args == NULL) {
free(argbuf);
return(0);
}
/* parse commandline into argc/argv format */
i = 0;
while (argbuf[i]) {
while (argbuf[i] == ' ')
i++;
if (argbuf[i]) {
if ((argbuf[i] == '\'') || (argbuf[i] == '"')) {
q = argbuf[i++];
if (!argbuf[i])
break;
} else
q = 0;
args[argc++] = &argbuf[i];
if (argc >= argc_max) {
argc_max += 64;
args = realloc(args, sizeof(char *)*argc_max);
if (args == NULL) {
free(argbuf);
return(0);
}
}
while ((argbuf[i]) && ((q)
? (argbuf[i]!=q) : (argbuf[i]!=' '))) i++;
if (argbuf[i]) {
argbuf[i] = 0;
i++;
}
}
}
args[argc] = NULL;
*argv = args;
return(argc);
}
/* For the Windows platform, this is the start of the application. */
int WINAPI
WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpszArg, int nCmdShow)
{
char **argv = NULL;
int argc, i;
wchar_t * AppID = L"86Box.86Box\0";
SetCurrentProcessExplicitAppUserModelID(AppID);
/* Check if Windows supports UTF-8 */
if (GetACP() == CP_UTF8)
acp_utf8 = 1;
else
acp_utf8 = 0;
/* Set this to the default value (windowed mode). */
video_fullscreen = 0;
/* We need this later. */
hinstance = hInst;
/* Set the application version ID string. */
sprintf(emu_version, "%s v%s", EMU_NAME, EMU_VERSION);
/* First, set our (default) language. */
set_language(0x0409);
/* Process the command line for options. */
argc = ProcessCommandLine(&argv);
/* Pre-initialize the system, this loads the config file. */
if (! pc_init(argc, argv)) {
/* Detach from console. */
if (force_debug)
CreateConsole(0);
if (source_hwnd)
PostMessage((HWND) (uintptr_t) source_hwnd, WM_HAS_SHUTDOWN, (WPARAM) 0, (LPARAM) hwndMain);
free(argbuf);
free(argv);
return(1);
}
/* Enable crash dump services. */
if (enable_crashdump)
InitCrashDump();
/* Create console window. */
if (force_debug) {
CreateConsole(1);
atexit(CloseConsole);
}
/* Handle our GUI. */
i = ui_init(nCmdShow);
free(argbuf);
free(argv);
return(i);
}
void
main_thread(void *param)
{
uint32_t old_time, new_time;
int drawits, frames;
framecountx = 0;
title_update = 1;
old_time = GetTickCount();
drawits = frames = 0;
while (!is_quit) {
/* See if it is time to run a frame of code. */
new_time = GetTickCount();
drawits += (new_time - old_time);
old_time = new_time;
if (drawits > 0 && !dopause) {
/* Yes, so do one frame now. */
drawits -= 10;
if (drawits > 50)
drawits = 0;
/* Run a block of code. */
pc_run();
/* Every 200 frames we save the machine status. */
if (++frames >= 200 && nvr_dosave) {
nvr_save();
nvr_dosave = 0;
frames = 0;
}
} else /* Just so we dont overload the host OS. */
Sleep(1);
/* If needed, handle a screen resize. */
if (doresize && !video_fullscreen) {
if (vid_resize & 2)
plat_resize(fixed_size_x, fixed_size_y);
else
plat_resize(scrnsz_x, scrnsz_y);
doresize = 0;
}
}
}
/*
* We do this here since there is platform-specific stuff
* going on here, and we do it in a function separate from
* main() so we can call it from the UI module as well.
*/
void
do_start(void)
{
LARGE_INTEGER qpc;
/* We have not stopped yet. */
is_quit = 0;
/* Initialize the high-precision timer. */
timeBeginPeriod(1);
QueryPerformanceFrequency(&qpc);
timer_freq = qpc.QuadPart;
win_log("Main timer precision: %llu\n", timer_freq);
/* Start the emulator, really. */
thMain = thread_create(main_thread, &is_quit);
SetThreadPriority(thMain, THREAD_PRIORITY_HIGHEST);
}
/* Cleanly stop the emulator. */
void
do_stop(void)
{
is_quit = 1;
plat_delay_ms(100);
if (source_hwnd)
PostMessage((HWND) (uintptr_t) source_hwnd, WM_HAS_SHUTDOWN, (WPARAM) 0, (LPARAM) hwndMain);
pc_close(thMain);
thMain = NULL;
}
void
plat_get_exe_name(char *s, int size)
{
wchar_t *temp;
if (acp_utf8)
GetModuleFileNameA(hinstance, s, size);
else {
temp = malloc(size * sizeof(wchar_t));
GetModuleFileNameW(hinstance, temp, size);
c16stombs(s, temp, size);
free(temp);
}
}
void
plat_tempfile(char *bufp, char *prefix, char *suffix)
{
SYSTEMTIME SystemTime;
if (prefix != NULL)
sprintf(bufp, "%s-", prefix);
else
strcpy(bufp, "");
GetSystemTime(&SystemTime);
sprintf(&bufp[strlen(bufp)], "%d%02d%02d-%02d%02d%02d-%03d%s",
SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay,
SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond,
SystemTime.wMilliseconds,
suffix);
}
int
plat_getcwd(char *bufp, int max)
{
wchar_t *temp;
if (acp_utf8)
(void)_getcwd(bufp, max);
else {
temp = malloc(max * sizeof(wchar_t));
(void)_wgetcwd(temp, max);
c16stombs(bufp, temp, max);
free(temp);
}
return(0);
}
int
plat_chdir(char *path)
{
wchar_t *temp;
int len, ret;
if (acp_utf8)
return(_chdir(path));
else {
len = mbstoc16s(NULL, path, 0) + 1;
temp = malloc(len * sizeof(wchar_t));
mbstoc16s(temp, path, len);
ret = _wchdir(temp);
free(temp);
return ret;
}
}
FILE *
plat_fopen(const char *path, const char *mode)
{
wchar_t *pathw, *modew;
int len;
FILE *fp;
if (acp_utf8)
return fopen(path, mode);
else {
len = mbstoc16s(NULL, path, 0) + 1;
pathw = malloc(sizeof(wchar_t) * len);
mbstoc16s(pathw, path, len);
len = mbstoc16s(NULL, mode, 0) + 1;
modew = malloc(sizeof(wchar_t) * len);
mbstoc16s(modew, mode, len);
fp = _wfopen(pathw, modew);
free(pathw);
free(modew);
return fp;
}
}
/* Open a file, using Unicode pathname, with 64bit pointers. */
FILE *
plat_fopen64(const char *path, const char *mode)
{
return plat_fopen(path, mode);
}
void
plat_remove(char *path)
{
wchar_t *temp;
int len;
if (acp_utf8)
remove(path);
else {
len = mbstoc16s(NULL, path, 0) + 1;
temp = malloc(len * sizeof(wchar_t));
mbstoc16s(temp, path, len);
_wremove(temp);
free(temp);
}
}
/* Make sure a path ends with a trailing (back)slash. */
void
plat_path_slash(char *path)
{
if ((path[strlen(path)-1] != '\\') &&
(path[strlen(path)-1] != '/')) {
strcat(path, "\\");
}
}
/* Check if the given path is absolute or not. */
int
plat_path_abs(char *path)
{
if ((path[1] == ':') || (path[0] == '\\') || (path[0] == '/'))
return(1);
return(0);
}
/* Return the last element of a pathname. */
char *
plat_get_basename(const char *path)
{
int c = (int)strlen(path);
while (c > 0) {
if (path[c] == '/' || path[c] == '\\')
return((char *)&path[c]);
c--;
}
return((char *)path);
}
/* Return the 'directory' element of a pathname. */
void
plat_get_dirname(char *dest, const char *path)
{
int c = (int)strlen(path);
char *ptr;
ptr = (char *)path;
while (c > 0) {
if (path[c] == '/' || path[c] == '\\') {
ptr = (char *)&path[c];
break;
}
c--;
}
/* Copy to destination. */
while (path < ptr)
*dest++ = *path++;
*dest = '\0';
}
char *
plat_get_filename(char *s)
{
int c = strlen(s) - 1;
while (c > 0) {
if (s[c] == '/' || s[c] == '\\')
return(&s[c+1]);
c--;
}
return(s);
}
char *
plat_get_extension(char *s)
{
int c = strlen(s) - 1;
if (c <= 0)
return(s);
while (c && s[c] != '.')
c--;
if (!c)
return(&s[strlen(s)]);
return(&s[c+1]);
}
void
plat_append_filename(char *dest, const char *s1, const char *s2)
{
strcpy(dest, s1);
plat_path_slash(dest);
strcat(dest, s2);
}
void
plat_put_backslash(char *s)
{
int c = strlen(s) - 1;
if (s[c] != '/' && s[c] != '\\')
s[c] = '/';
}
int
plat_dir_check(char *path)
{
DWORD dwAttrib;
int len;
wchar_t *temp;
if (acp_utf8)
dwAttrib = GetFileAttributesA(path);
else {
len = mbstoc16s(NULL, path, 0) + 1;
temp = malloc(len * sizeof(wchar_t));
mbstoc16s(temp, path, len);
dwAttrib = GetFileAttributesW(temp);
free(temp);
}
return(((dwAttrib != INVALID_FILE_ATTRIBUTES &&
(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))) ? 1 : 0);
}
int
plat_dir_create(char *path)
{
int ret, len;
wchar_t *temp;
if (acp_utf8)
return (int)SHCreateDirectoryExA(NULL, path, NULL);
else {
len = mbstoc16s(NULL, path, 0) + 1;
temp = malloc(len * sizeof(wchar_t));
mbstoc16s(temp, path, len);
ret = (int)SHCreateDirectoryExW(NULL, temp, NULL);
free(temp);
return ret;
}
}
uint64_t
plat_timer_read(void)
{
LARGE_INTEGER li;
QueryPerformanceCounter(&li);
return(li.QuadPart);
}
static LARGE_INTEGER
plat_get_ticks_common(void)
{
LARGE_INTEGER EndingTime, ElapsedMicroseconds;
if (first_use) {
QueryPerformanceFrequency(&Frequency);
QueryPerformanceCounter(&StartingTime);
first_use = 0;
}
QueryPerformanceCounter(&EndingTime);
ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
/* We now have the elapsed number of ticks, along with the
number of ticks-per-second. We use these values
to convert to the number of elapsed microseconds.
To guard against loss-of-precision, we convert
to microseconds *before* dividing by ticks-per-second. */
ElapsedMicroseconds.QuadPart *= 1000000;
ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;
return ElapsedMicroseconds;
}
uint32_t
plat_get_ticks(void)
{
return (uint32_t)(plat_get_ticks_common().QuadPart / 1000);
}
uint32_t
plat_get_micro_ticks(void)
{
return (uint32_t)plat_get_ticks_common().QuadPart;
}
void
plat_delay_ms(uint32_t count)
{
Sleep(count);
}
/* Return the VIDAPI number for the given name. */
int
plat_vidapi(char *name)
{
int i;
/* Default/System is SDL Hardware. */
if (!strcasecmp(name, "default") || !strcasecmp(name, "system")) return(1);
/* If DirectDraw or plain SDL was specified, return SDL Software. */
if (!strcasecmp(name, "ddraw") || !strcasecmp(name, "sdl")) return(1);
for (i = 0; i < RENDERERS_NUM; i++) {
if (vid_apis[i].name &&
!strcasecmp(vid_apis[i].name, name)) return(i);
}
/* Default value. */
return(1);
}
/* Return the VIDAPI name for the given number. */
char *
plat_vidapi_name(int api)
{
char *name = "default";
switch(api) {
case 0:
name = "sdl_software";
break;
case 1:
break;
case 2:
name = "sdl_opengl";
break;
#ifdef DEV_BRANCH /* feature-opengl */
case 3:
name = "opengl_core";
break;
#else
case 3:
name = "sdl_opengl"; /* fall back to SDL_OpenGL */
break;
#endif
#ifdef USE_VNC
case 4:
name = "vnc";
break;
#endif
default:
fatal("Unknown renderer: %i\n", api);
break;
}
return(name);
}
int
plat_setvid(int api)
{
int i;
win_log("Initializing VIDAPI: api=%d\n", api);
startblit();
/* Close the (old) API. */
vid_apis[vid_api].close();
vid_api = api;
if (vid_apis[vid_api].local)
ShowWindow(hwndRender, SW_SHOW);
else
ShowWindow(hwndRender, SW_HIDE);
/* Initialize the (new) API. */
i = vid_apis[vid_api].init((void *)hwndRender);
endblit();
if (! i) return(0);
device_force_redraw();
vid_api_inited = 1;
return(1);
}
/* Tell the renderers about a new screen resolution. */
void
plat_vidsize(int x, int y)
{
if (!vid_api_inited || !vid_apis[vid_api].resize) return;
startblit();
vid_apis[vid_api].resize(x, y);
endblit();
}
void
plat_vidapi_enable(int enable)
{
int i = 1;
if (!vid_api_inited || !vid_apis[vid_api].enable)
return;
vid_apis[vid_api].enable(enable != 0);
if (! i)
return;
if (enable)
device_force_redraw();
}
int
get_vidpause(void)
{
return(vid_apis[vid_api].pause());
}
void
plat_setfullscreen(int on)
{
RECT rect;
int temp_x, temp_y;
int dpi = win_get_dpi(hwndMain);
/* Are we changing from the same state to the same state? */
if ((!!on) == (!!video_fullscreen))
return;
if (on && video_fullscreen_first) {
video_fullscreen |= 2;
if (ui_msgbox_header(MBX_INFO | MBX_DONTASK, (wchar_t *) IDS_2134, (wchar_t *) IDS_2052) == 10) {
video_fullscreen_first = 0;
config_save();
}
video_fullscreen &= 1;
}
/* OK, claim the video. */
win_mouse_close();
/* Close the current mode, and open the new one. */
video_fullscreen = on | 2;
if (vid_apis[vid_api].set_fs)
vid_apis[vid_api].set_fs(on);
if (!on) {
plat_resize(scrnsz_x, scrnsz_y);
if (vid_resize) {
/* scale the screen base on DPI */
if (!(vid_resize & 2) && window_remember) {
MoveWindow(hwndMain, window_x, window_y, window_w, window_h, TRUE);
GetClientRect(hwndMain, &rect);
temp_x = rect.right - rect.left + 1;
temp_y = rect.bottom - rect.top + 1 - sbar_height;
} else {
if (dpi_scale) {
temp_x = MulDiv((vid_resize & 2) ? fixed_size_x : unscaled_size_x, dpi, 96);
temp_y = MulDiv((vid_resize & 2) ? fixed_size_y : unscaled_size_y, dpi, 96);
} else {
temp_x = (vid_resize & 2) ? fixed_size_x : unscaled_size_x;
temp_y = (vid_resize & 2) ? fixed_size_y : unscaled_size_y;
}
/* Main Window. */
ResizeWindowByClientArea(hwndMain, temp_x, temp_y + sbar_height);
}
/* Render window. */
MoveWindow(hwndRender, 0, 0, temp_x, temp_y, TRUE);
GetWindowRect(hwndRender, &rect);
/* Status bar. */
MoveWindow(hwndSBAR, 0, rect.bottom, temp_x, 17, TRUE);
if (mouse_capture)
ClipCursor(&rect);
scrnsz_x = (vid_resize & 2) ? fixed_size_x : unscaled_size_x;
scrnsz_y = (vid_resize & 2) ? fixed_size_y : unscaled_size_y;
}
}
video_fullscreen &= 1;
video_force_resize_set(1);
if (!on)
doresize = 1;
win_mouse_init();
/* Release video and make it redraw the screen. */
device_force_redraw();
/* Send a CTRL break code so CTRL does not get stuck. */
keyboard_input(0, 0x01D);
/* Finally, handle the host's mouse cursor. */
/* win_log("%s full screen, %s cursor\n", on ? "enter" : "leave", on ? "hide" : "show"); */
show_cursor(video_fullscreen ? 0 : -1);
/* This is needed for OpenGL. */
plat_vidapi_enable(0);
plat_vidapi_enable(1);
}
void
plat_vid_reload_options(void)
{
if (!vid_api_inited || !vid_apis[vid_api].reload)
return;
vid_apis[vid_api].reload();
}
void
take_screenshot(void)
{
startblit();
screenshots++;
endblit();
device_force_redraw();
}
/* LPARAM interface to plat_get_string(). */
LPARAM win_get_string(int id)
{
wchar_t *ret;
ret = plat_get_string(id);
return ((LPARAM) ret);
}
void /* plat_ */
startblit(void)
{
WaitForSingleObject(ghMutex, INFINITE);
}
void /* plat_ */
endblit(void)
{
ReleaseMutex(ghMutex);
}