/* dos.h
 *
 * Code to detect the surrounding DOS/Windows environment and support routines to work with it
 * (C) 2009-2012 Jonathan Campbell.
 * Hackipedia DOS library.
 *
 * This code is licensed under the LGPL.
 * <insert LGPL legal text here>
 */

#ifndef __HW_DOS_DOS_H
#define __HW_DOS_DOS_H

#include <hw/cpu/cpu.h>
#include <stdint.h>

#if !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
/* NTVDM.EXE DOSNTAST.VDD call support */
#include <windows/ntvdm/ntvdmlib.h>
#endif

#if defined(TARGET_OS2)
# define INCL_DOSMISC
# ifdef FAR /* <- conflict between OS/2 headers and cpu.h definition of "FAR" */
#  undef FAR
# endif
# include <os2.h>
#endif

extern unsigned char FAR *dos_LOL;

#if TARGET_MSDOS == 32 && !defined(TARGET_OS2)
extern int8_t dpmi_no_0301h; /* -1 = not tested 0 = avail 1 = N/A */
#else
# define dpmi_no_0301h 0 /* FIXME: Is it possible for DOS extenders to run non-4GW non-LE executables? */
#endif

#define DPMI_ENTER_AUTO 0xFF

/* DOS "Flavor" we are running under.
 * I originally didn't care too much until one day some really strange
 * fatal bugs popped up when running this code under FreeDOS 1.0, almost
 * as if the FreeDOS kernel does something to fuck with the DOS extender's
 * mind if our code attempts certain things like reading the ROM area... */
enum {
	DOS_FLAVOR_NONE=0,		/* generic DOS */
	DOS_FLAVOR_MSDOS,		/* Microsoft MS-DOS */
	DOS_FLAVOR_FREEDOS,		/* FreeDOS */
};

extern uint8_t dos_flavor;
extern uint16_t dos_version;
extern const char *dos_version_method;
extern uint32_t freedos_kernel_version;
extern unsigned char vcpi_present;
extern unsigned char vcpi_major_version,vcpi_minor_version;

#if TARGET_MSDOS == 32
const char *
#else
const char far *
#endif
dos_get_freedos_kernel_version_string();

#if defined(TARGET_WINDOWS) && TARGET_MSDOS == 32
void win32_probe_for_wine(); /* Probing for WINE from the Win32 layer */
#elif defined(TARGET_WINDOWS) && TARGET_MSDOS == 16
void win16_probe_for_wine(); /* Probing for WINE from the Win16 layer */
#endif

#if TARGET_MSDOS == 32 && !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
void mux_realmode_2F_call(struct dpmi_realmode_call *rc);
#endif
#if TARGET_MSDOS == 16 && defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
void mux_realmode_2F_call(struct dpmi_realmode_call far *rc);
#endif

#if TARGET_MSDOS == 32
void *dpmi_phys_addr_map(uint32_t phys,uint32_t size);
void dpmi_phys_addr_free(void *base);
#endif

#if TARGET_MSDOS == 32 && !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
unsigned int dpmi_test_rm_entry_call(struct dpmi_realmode_call *rc);
#endif

struct dos_mcb_enum {
	uint16_t	segment;
	uint16_t	counter;
	/* acquired data */
	unsigned char FAR *ptr;	/* pointer to actual memory content */
	uint16_t	size,psp,cur_segment;
	uint8_t		type;
	char		name[9];
};

#pragma pack(push,1)
struct dpmi_realmode_call {
	uint32_t	edi,esi,ebp,reserved;
	uint32_t	ebx,edx,ecx,eax;
	uint16_t	flags,es,ds,fs,gs,ip,cs,sp,ss;
};
#pragma pack(pop)

#ifndef TARGET_OS2
# if TARGET_MSDOS == 32
/* WARNING: This is only 100% reliable if the memory in question is below the 1MB mark!
 *          This may happen to work for pointers above the 1MB mark because DOS4GW and DOS32a tend to
 *          allocate that way, but that 1:1 correspondence is not guaranteed */
static inline uint32_t ptr2phys_low1mb(unsigned char *x) {
	return (uint32_t)x;
}
# else
static inline uint32_t ptr2phys_low1mb(unsigned char far *x) {
	uint32_t r = (uint32_t)FP_SEG(x) << 4UL;
	return r + (uint32_t)FP_OFF(x);
}
# endif
#endif

#if TARGET_MSDOS == 16 && !defined(TARGET_OS2)
static inline void far *normalize_realmode_far_ptr(void far *p) {
	return MK_FP(
		FP_SEG(p) + (FP_OFF(p) >> 4),
		FP_OFF(p) & 0xF);
}
#endif

#ifndef TARGET_OS2
# if TARGET_MSDOS == 32
int _dos_xread(int fd,void *buffer,int bsz);
# else
int _dos_xread(int fd,void far *buffer,int bsz);
# endif

# if TARGET_MSDOS == 32
int _dos_xwrite(int fd,void *buffer,int bsz);
# else
int _dos_xwrite(int fd,void far *buffer,int bsz);
# endif
#endif

#if TARGET_MSDOS == 32 && !defined(TARGET_OS2)
#  define dpmi_alloc_descriptor() dpmi_alloc_descriptors(1)

void *dpmi_alloc_dos(unsigned long len,uint16_t *selector);
void dpmi_free_dos(uint16_t selector);

void dpmi_free_descriptor(uint16_t d);
uint16_t dpmi_alloc_descriptors(uint16_t c);
unsigned int dpmi_set_segment_base(uint16_t sel,uint32_t base);
unsigned int dpmi_set_segment_limit(uint16_t sel,uint32_t limit);
unsigned int dpmi_set_segment_access(uint16_t sel,uint16_t access);
void *dpmi_phys_addr_map(uint32_t phys,uint32_t size);
void dpmi_phys_addr_free(void *base);
#endif

#if TARGET_MSDOS == 32
unsigned char *dos_list_of_lists();
#else
unsigned char far *dos_list_of_lists();
#endif

#if TARGET_MSDOS == 32 && !defined(TARGET_OS2)
int dpmi_alternate_rm_call(struct dpmi_realmode_call *rc);
int dpmi_alternate_rm_call_stacko(struct dpmi_realmode_call *rc);
#endif

#if TARGET_MSDOS == 32 && !defined(TARGET_OS2)
# if defined(TARGET_WINDOWS)
/* as a 32-bit Windows program: even if DPMI is present, it's useless to us because we can't call into that part of Windows */
#  define dpmi_present 0
# endif
#endif
#if TARGET_MSDOS == 16 || (TARGET_MSDOS == 32 && !defined(TARGET_WINDOWS))
/* as a 16-bit program (DOS or Windows), DPMI might be present. Note that DPMI can be present even under NTVDM.EXE under Windows NT,
 * because NTVDM.EXE will emulate some DPMI functions. */
extern unsigned char dpmi_present;
extern uint16_t dpmi_flags;
extern unsigned char dpmi_init;
extern uint32_t dpmi_entry_point; /* NTS: This is the real-mode address, even for 32-bit builds */
extern unsigned char dpmi_processor_type;
extern uint16_t dpmi_version;
extern uint16_t dpmi_private_data_length_paragraphs;
extern uint16_t dpmi_private_data_segment;
extern unsigned char dpmi_entered;	/* 0=not yet entered, 16=entered as 16bit, 32=entered as 32bit */
extern uint64_t dpmi_rm_entry;
extern uint32_t dpmi_pm_entry;
extern uint16_t dpmi_pm_cs,dpmi_pm_ds,dpmi_pm_es,dpmi_pm_ss;

void __cdecl dpmi_enter_core(); /* Watcom's inline assembler is too limiting to carry out the DPMI entry and switch back */
#endif

#if TARGET_MSDOS == 16 && !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
int dpmi_private_alloc();
int dpmi_enter(unsigned char mode);
#endif

void probe_dos();
void probe_dpmi();
int probe_vcpi();

uint16_t dos_mcb_first_segment();
int mcb_name_is_junk(char *s/*8 char*/);
int dos_mcb_next(struct dos_mcb_enum *e);
int dos_mcb_first(struct dos_mcb_enum *e);
void mcb_filter_name(struct dos_mcb_enum *e);
unsigned char FAR *dos_mcb_get_psp(struct dos_mcb_enum *e);

struct dos_psp_cooked {
	unsigned char FAR *raw;
	uint16_t	memsize,callpsp,env;
	char		cmd[130];
};

int dos_parse_psp(uint16_t seg,struct dos_psp_cooked *e);

struct dos_device_enum {
	unsigned char FAR *raw,FAR *next;
	uint16_t		ns,no,attr,entry,intent,count;
	char			name[9];
};

int dos_device_first(struct dos_device_enum *e);
int dos_device_next(struct dos_device_enum *e);

#if TARGET_MSDOS == 16 && !defined(TARGET_OS2)
uint32_t dos_linear_to_phys_vcpi(uint32_t pn);
#endif

#if TARGET_MSDOS == 32
extern struct dos_linear_to_phys_info dos_ltp_info;
extern unsigned char dos_ltp_info_init;

int dos_ltp_probe();

/* NTS: The return value is 64-bit so that in scenarios where we hack DPMI to support PSE and PAE modes,
 *      the function will still return the physical address associated with the page even when it's above
 *      the 4GB boundary. But as a 32-bit DOS program, the linear addresses will never exceed 32-bit. */
uint64_t dos_linear_to_phys(uint32_t linear);

int dpmi_linear_lock(uint32_t lin,uint32_t size);
int dpmi_linear_unlock(uint32_t lin,uint32_t size);
void *dpmi_linear_alloc(uint32_t try_lin,uint32_t size,uint32_t flags,uint32_t *handle);
int dpmi_linear_free(uint32_t handle);

#define DOS_LTP_FAILED 0xFFFFFFFFFFFFFFFFULL

struct dos_linear_to_phys_info {
	unsigned char		paging:1;		/* paging is enabled, therefore mapping will occur. if not set, then linear == physical memory addresses */
	unsigned char		dos_remap:1;		/* if set, the lower 1MB region (DOS conventional memory) is remapped. if clear, we can assume 1:1 mapping below 1MB */
	unsigned char		should_lock_pages:1;	/* if set, the program should call DPMI functions to lock pages before attempting to get physical memory address */
	unsigned char		cant_xlate:1;		/* if set, resources to determine physical memory addresses are not available (such as: running in a Windows DOS Box). however dos_remap=0 means we can assume 1:1 mapping below 1MB */
	unsigned char		using_pae:1;		/* if set, the DOS extender or DPMI host has PAE/PSE extensions enabled. This changes how page tables are parsed, and can prevent us from mapping */
	unsigned char		dma_dos_xlate:1;	/* usually set if dos_remap=1 to say the DOS extender or environment translates DMA addresses (i.e. Windows DOS Box), but we can't actually know the physical memory address. We can do DMA from DOS memory */
	unsigned char		vcpi_xlate:1;		/* use VCPI to translate linear -> phys */
	unsigned char		reserved:1;
	uint32_t		cr0;
	uint32_t		cr3;			/* last known copy of the CR3 (page table base) register */
	uint32_t		cr4;			/* last known copy of the CR4 register */
};
#endif

#define BIOS_KS_ALT		0x08
#define BIOS_KT_CTRL		0x04

static inline unsigned char read_bios_keystate() { /* from 0x40:0x17 */
#if TARGET_MSDOS == 32
	return *((unsigned char*)(0x400 + 0x17));
#else
	return *((unsigned char far*)MK_FP(0x40,0x17));
#endif
}

#if defined(TARGET_WINDOWS) && TARGET_MSDOS == 16
void far *win16_getexhandler(unsigned char n);
int win16_setexhandler(unsigned char n,void far *x);
void far *win16_getvect(unsigned char n);
int win16_setvect(unsigned char n,void far *x);
#endif

#if defined(TARGET_WINDOWS) && TARGET_MSDOS == 32
typedef struct Win32OrdinalLookupInfo {
	DWORD	entries,base,base_addr;
	DWORD*	table;
} Win32OrdinalLookupInfo;

DWORD *Win32GetExportOrdinalTable(HMODULE mod,DWORD *entries,DWORD *base,DWORD *base_addr);
void *Win32GetOrdinalAddress(Win32OrdinalLookupInfo *nfo,unsigned int ord);
int Win32GetOrdinalLookupInfo(HMODULE mod,Win32OrdinalLookupInfo *info);
#endif

#if defined(TARGET_WINDOWS) && TARGET_MSDOS == 16
extern DWORD		genthunk32w_ntdll;
extern DWORD		genthunk32w_kernel32;
extern DWORD		genthunk32w_kernel32_GetVersion;
extern DWORD		genthunk32w_kernel32_GetVersionEx;
extern DWORD		genthunk32w_kernel32_GetLastError;
extern BOOL		__GenThunksExist;
extern BOOL		__GenThunksChecked;
extern DWORD		(PASCAL FAR *__LoadLibraryEx32W)(LPCSTR lpName,DWORD a,DWORD b);
extern BOOL		(PASCAL FAR *__FreeLibrary32W)(DWORD hinst);
extern DWORD		(PASCAL FAR *__GetProcAddress32W)(DWORD hinst,LPCSTR name);
extern DWORD		(PASCAL FAR *__GetVDMPointer32W)(LPVOID ptr,UINT mask);
extern DWORD		(_cdecl _far *__CallProcEx32W)(DWORD params,DWORD convertMask,DWORD procaddr32,...);

/* NOTE: You call it as if it were declared CallProc32W(..., DWORD hinst,DWORD convertMask,DWORD procaddr32); Ick */
extern DWORD		(PASCAL FAR *__CallProc32W)(DWORD hinst,DWORD convertMask,DWORD procaddr32,...);

/* it would be nice if Open Watcom defined these constants for Win16 */
#define CPEX_DEST_STDCALL	0x00000000UL
#define CPEX_DEST_CDECL		0x80000000UL

int genthunk32_init();
void genthunk32_free();
#endif

#if TARGET_MSDOS == 16 || !defined(TARGET_WINDOWS)
#pragma pack(push,4)
/* OpenWatcom does not define the OSVERSIONINFO struct for Win16 */
typedef struct OSVERSIONINFO {
	uint32_t	dwOSVersionInfoSize;
	uint32_t	dwMajorVersion;
	uint32_t	dwMinorVersion;
	uint32_t	dwBuildNumber;
	uint32_t	dwPlatformId;
	char		szCSDVersion[128];
} OSVERSIONINFO;

#define MAXPNAMELEN     	32

#define WAVECAPS_PITCH          0x0001
#define WAVECAPS_PLAYBACKRATE   0x0002
#define WAVECAPS_VOLUME         0x0004
#define WAVECAPS_LRVOLUME       0x0008
#define WAVECAPS_SYNC           0x0010
#define WAVECAPS_SAMPLEACCURATE	0x0020

typedef struct WAVEOUTCAPS {
	uint16_t	wMid;
	uint16_t	wPid;
	uint32_t	vDriverVersion;
	char		szPname[MAXPNAMELEN];
	uint32_t	dwFormats;
	uint16_t	wChannels;
	uint16_t	wReserved1;
	uint32_t	dwSupport;
} WAVEOUTCAPS;
#pragma pack(pop)
#endif

#if !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
void far *dpmi_getexhandler(unsigned char n);
int dpmi_setexhandler(unsigned char n,void far *x);
#endif

#if TARGET_MSDOS == 32 && !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
/* TODO: This should be moved into the hw/DOS library */
extern unsigned char		nmi_32_hooked;
extern int			nmi_32_refcount;
extern void			(interrupt *nmi_32_old_vec)();

void do_nmi_32_unhook();
void do_nmi_32_hook();
#endif

#if defined(TARGET_MSDOS) && !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
enum {
	DOS_CLOSE_AWARENESS_NOT_ACK=0,
	DOS_CLOSE_AWARENESS_ACKED=1
};

void dos_vm_yield();
void dos_close_awareness_ack();
int dos_close_awareness_query();
void dos_close_awareness_cancel();
int dos_close_awareness_available();
int dos_close_awareness_enable(unsigned char en);
#endif

/* unlike DOSBox, VirtualBox's ROM BIOS contains it's version number, which we copy down here */
extern char virtualbox_version_str[64];

int detect_virtualbox_emu();

#if TARGET_MSDOS == 16 && !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
int __cdecl dpmi_lin2fmemcpy_32(unsigned char far *dst,uint32_t lsrc,uint32_t sz);
int __cdecl dpmi_lin2fmemcpy_16(unsigned char far *dst,uint32_t lsrc,uint32_t sz);
int dpmi_lin2fmemcpy(unsigned char far *dst,uint32_t lsrc,uint32_t sz);
int dpmi_lin2fmemcpy_init();
#endif

struct lib_dos_options {
	uint8_t			dont_load_dosntast:1;	/* do not automatically load DOSNTAST, but use it if loaded. */
							/* if not loaded and the program wants it later on, it should
							 * call ntvdm_dosntast_load_vdd(); */
	uint8_t			dont_use_dosntast:1;	/* do not use DOSNTAST, even if loaded */
	uint8_t			__reserved__:6;
};

extern struct lib_dos_options lib_dos_option;

# define DOSNTAST_HANDLE_UNASSIGNED		0xFFFFU

# define DOSNTAST_INIT_REPORT_HANDLE		0xD0500000
# define DOSNTAST_INIT_REPORT_HANDLE_C		0xD0500000ULL
/* in: EBX = DOSNTAST_INIT_REPORT_HANDLE
 *     ECX = NTVDM handle
 * out: EBX = 0x55AA55AA
 *      ECX = flat memory address where signature is stored (must be in BIOS data area) */

# define DOSNTAST_GETVERSIONEX			0xD0500001
# define DOSNTAST_GETVERSIONEX_C		0xD0500001ULL
/* in: EBX = <command>
 *     ECX = protected mode call (1) or real-mode call (0)
 *     DS:ESI = OSVERSIONINFO struct
 * out: EBX = result
 *     DS:ESI = filled in with OS struct */

# define DOSNTAST_GET_TICK_COUNT		0xD0500002
# define DOSNTAST_GET_TICK_COUNT_C		0xD0500002ULL
/* in: EBX = <command>
 * out: EBX = tick count */

# define DOSNTAST_GET_IO_PORT			0xD0500003
# define DOSNTAST_GET_IO_PORT_C			0xD0500003ULL
/* in: EBX = <command>
 * out: EBX = 0x55AA55AA
 *      EDX = I/O port base */

# define DOSNTAST_NOTIFY_UNLOAD			0xD050FFFF
# define DOSNTAST_NOTIFY_UNLOAD_C		0xD050FFFFULL
/* in: EBX = <command>
 * out: EBX = none */

# define DOSNTAST_FUNCTION_GENERAL		0x1000
#  define DOSNTAST_FUN_GEN_SUB_MESSAGEBOX	0x0000

# define DOSNTAST_FUNCTION_WINMM			0x1001
#  define DOSNTAST_FUN_WINMM_SUB_waveOutGetNumDevs	0x0000
#  define DOSNTAST_FUN_WINMM_SUB_waveOutGetDevCaps	0x0001
#  define DOSNTAST_FUN_WINMM_SUB_waveOutOpen		0x0002

const char *dos_flavor_str(uint8_t f);

/* Windows NT-friendly version of Win386 MapAliasToFlat.
 * The library version is naive and assumes Windows 3.x/9x/ME behavior.
 * If you need to convert pointers NOT given by Win386's AllocAlias() functions
 * (such as 16:16 pointers given by Window messages) and need the code to gracefully
 * handle itself under Windows NT, use this function not MapAliasToFlat() */
#if TARGET_MSDOS == 32 && defined(WIN386)
void far *win386_alt_winnt_MapAliasToFlat(DWORD farptr);
void far *win386_help_MapAliasToFlat(DWORD farptr);
#endif

#if (TARGET_MSDOS == 16 || TARGET_MSDOS == 32) && !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
extern unsigned short			smartdrv_version;
extern int				smartdrv_fd;

int smartdrv_close();
int smartdrv_flush();
int smartdrv_detect();
#endif

#if defined(NTVDM_CLIENT) && !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
uint16_t ntvdm_dosntast_detect();
int ntvdm_dosntast_load_vdd();
void ntvdm_dosntast_unload();
#endif

#if defined(NTVDM_CLIENT) && !defined(TARGET_WINDOWS) && !defined(TARGET_OS2)
void detect_windows_ntdvm_dosntast_init_func();
extern void (*detect_windows_ntdvm_dosntast_init_CB)();

static inline void detect_window_enable_ntdvm() {
	detect_windows_ntdvm_dosntast_init_CB = detect_windows_ntdvm_dosntast_init_func;
}
#else
# define detect_window_enable_ntdvm()
#endif

#if defined(TARGET_WINDOWS) && TARGET_MSDOS == 32 && !defined(WIN386)
int Win9xQT_ThunkInit();
void Win9xQT_ThunkFree();
#endif

#if defined(TARGET_WINDOWS) && TARGET_MSDOS == 32 && !defined(WIN386)
int probe_dos_version_win9x_qt_thunk_func();
extern int (*probe_dos_version_win9x_qt_thunk_CB)();
static inline void detect_dos_version_enable_win9x_qt_thunk() {
	probe_dos_version_win9x_qt_thunk_CB = probe_dos_version_win9x_qt_thunk_func;
}
#else
# define detect_dos_version_enable_win9x_qt_thunk()
#endif

uint32_t dos_linear_to_phys_vcpi(uint32_t pn);

#endif /* __HW_DOS_DOS_H */

