/*
 *  Copyright (C) 2002-2021  The DOSBox Team
 *
 *  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 <string.h>

#include "control.h"
#include "dosbox.h"
#include "callback.h"
#include "regs.h"
#include "mem.h"
#include "bios.h"
#include "dos_inc.h"
#include "support.h"
#include "parport.h"
#include "drives.h" //Wildcmp
#include "render.h"
/* Include all the devices */

unsigned int BeepDuration();

#include "dev_con.h"
#include <fstream>

#if (!defined(WIN32) && defined(C_SDL2)) || defined(MACOSX)
typedef char host_cnv_char_t;
host_cnv_char_t *CodePageGuestToHost(const char *s);
#endif

DOS_Device * Devices[DOS_DEVICES] = {NULL};
extern std::map<int, int> lowboxdrawmap;
extern int dos_clipboard_device_access;
extern bool morelen, halfwidthkana, showdbcs;
extern const char * dos_clipboard_device_name;
bool isDBCSCP(), shiftjis_lead_byte(int c);
#if !defined(OSFREE)
bool Network_IsNetworkResource(const char * filename), TTF_using(void);
#endif
bool CodePageGuestToHostUTF16(uint16_t *d/*CROSS_LEN*/,const char *s/*CROSS_LEN*/);


bool DOS_ExtDevice::CheckSameDevice(uint16_t seg, uint16_t s_off, uint16_t i_off) {
	if(seg == ext.segment && s_off == ext.strategy && i_off == ext.interrupt) {
		return true;
	}
	return false;
}

/* This function is optimized for use with read/write and ioctl read/write commands for character devices */
uint16_t DOS_ExtDevice::CallRWIODeviceFunction(uint8_t command, uint8_t length, uint16_t seg, uint16_t offset, uint16_t size) {
	uint16_t oldbx = reg_bx;
	uint16_t oldds = SegValue(ds);
	uint16_t oldes = SegValue(es);

	real_writeb(dos.dcp, 0, length);
	real_writeb(dos.dcp, 1, 0);
	real_writeb(dos.dcp, 2, command);
	real_writew(dos.dcp, 3, 0);
	real_writed(dos.dcp, 5, 0);
	real_writed(dos.dcp, 9, 0);
	if (length >= 20) { // this code is used below for input/output status which does not use these fields
		real_writeb(dos.dcp, 13, 0);
		real_writew(dos.dcp, 14, offset);
		real_writew(dos.dcp, 16, seg);
		real_writew(dos.dcp, 18, size);
	}

	reg_bx = 0;
	SegSet16(ds, ext.segment);
	SegSet16(es, dos.dcp);
	CALLBACK_RunRealFar(ext.segment, ext.strategy);
	CALLBACK_RunRealFar(ext.segment, ext.interrupt);
	reg_bx = oldbx;
	SegSet16(es, oldes);
	SegSet16(ds, oldds);

	return real_readw(dos.dcp, 3);
}

bool DOS_ExtDevice::ReadFromControlChannel(PhysPt bufptr,uint16_t size,uint16_t * retcode) {
	if(ext.attribute & DeviceAttributeFlags::SupportsIoctl) {
		// IOCTL INPUT
		if((CallRWIODeviceFunction(DEVFUNC_IOCTL_READ, 26, (uint16_t)(bufptr >> 4), (uint16_t)(bufptr & 0x000f), size) & 0x8000) == 0) {
			*retcode = real_readw(dos.dcp, 18);
			return true;
		}
	}
	return false;
}

bool DOS_ExtDevice::WriteToControlChannel(PhysPt bufptr,uint16_t size,uint16_t * retcode) {
	if(ext.attribute & DeviceAttributeFlags::SupportsIoctl) {
		// IOCTL OUTPUT
		if((CallRWIODeviceFunction(DEVFUNC_IOCTL_WRITE, 26, (uint16_t)(bufptr >> 4), (uint16_t)(bufptr & 0x000f), size) & 0x8000) == 0) {
			*retcode = real_readw(dos.dcp, 18);
			return true;
		}
	}
	return false;
}

bool DOS_ExtDevice::Read(uint8_t * data,uint16_t * size) {
	PhysPt bufptr = (dos.dcp << 4) + 32;

	if (dos.dcp_size_seg <= 2) return false;

	unsigned int batch_size = (dos.dcp_size_seg - 2) << 4;
	unsigned int todo = *size;
	unsigned int done = 0;
	unsigned int rd;

	const auto inproc = [bufptr, &todo, &done, &rd, &data, this](const unsigned int batch_size) {
		rd = 0;

		if (CallRWIODeviceFunction(DEVFUNC_READ, 26, dos.dcp + 2/*2 paras = 32 bytes*/, 0, batch_size) & 0x8000/*error*/)
			return;

		rd = real_readw(dos.dcp, 18);/*how much was read?*/
		if (rd > batch_size) {
			LOG(LOG_MISC,LOG_DEBUG)("Device driver read too much data!");
			rd = 0;
			return;
		}
		for (unsigned int c=0;c < rd;c++) data[c] = mem_readb(bufptr+c);
		todo -= rd;
		data += rd;
		done += rd;
	};

	while (todo >= batch_size) {
		inproc(batch_size);
		if (rd != batch_size) {
			*size = done;
			return false;
		}
	}

	if (todo != 0) {
		inproc(todo);
		if (rd != todo) {
			*size = done;
			return false;
		}
	}

	*size = done;
	return true;
}

bool DOS_ExtDevice::Write(const uint8_t * data,uint16_t * size) {
	PhysPt bufptr = (dos.dcp << 4) + 32;

	if (dos.dcp_size_seg <= 2) return false;

	unsigned int batch_size = (dos.dcp_size_seg - 2) << 4;
	unsigned int todo = *size;
	unsigned int done = 0;
	unsigned int wd;

	const auto inproc = [bufptr, &todo, &done, &wd, &data, this](const unsigned int batch_size) {
		wd = 0;

		for (unsigned int c=0;c < batch_size;c++) mem_writeb(bufptr+c,data[c]);
		if (CallRWIODeviceFunction(DEVFUNC_WRITE, 26, dos.dcp + 2/*2 paras = 32 bytes*/, 0, batch_size) & 0x8000/*error*/)
			return;

		wd = real_readw(dos.dcp, 18);/*how much was written?*/
		if (wd > batch_size) {
			LOG(LOG_MISC,LOG_DEBUG)("Device driver wrote too much data!");
			wd = 0;
			return;
		}
		todo -= wd;
		data += wd;
		done += wd;
	};

	while (todo >= batch_size) {
		inproc(batch_size);
		if (wd != batch_size) {
			*size = done;
			return false;
		}
	}

	if (todo != 0) {
		inproc(todo);
		if (wd != todo) {
			*size = done;
			return false;
		}
	}

	*size = done;
	return true;

}

bool DOS_ExtDevice::Close() {
	return true;
}

bool DOS_ExtDevice::Seek(uint32_t * pos,uint32_t type) {
	(void)pos;//UNUSED
	(void)type;//UNUSED
	return true;
}

uint16_t DOS_ExtDevice::GetInformation(void) {
	uint16_t ret = DeviceInfoFlags::ExternalDevice;
	if (ext.attribute & DeviceAttributeFlags::CharacterDevice)   ret |= DeviceInfoFlags::Device;
	if (ext.attribute & DeviceAttributeFlags::SupportsIoctl)     ret |= DeviceInfoFlags::IoctlSupport;
	if (ext.attribute & DeviceAttributeFlags::SupportsRemovable) ret |= DeviceInfoFlags::OpenCloseSupport;
	ret |= ext.attribute & DeviceAttributeFlags::CoreDevicesMask;
	return ret;
}

uint8_t DOS_ExtDevice::GetStatus(bool input_flag) {
	uint16_t status;
	if(input_flag) {
		// INPUT STATUS
		status = CallRWIODeviceFunction(DEVFUNC_INPUT_STATUS, 13, 0, 0, 0);
	} else {
		// OUTPUT STATUS
		status = CallRWIODeviceFunction(DEVFUNC_OUTPUT_STATUS, 13, 0, 0, 0);
	}
	// check NO ERROR & BUSY
	if((status & 0x8200) == 0) {
		return 0xff;
	}
	return 0x00;
}

uint32_t DOS_CheckExtDevice(const char *name, bool already_flag) {
	uint32_t addr = dos_infoblock.GetStartOfDeviceChain();
	uint16_t seg, off;
	uint16_t next_seg, next_off;
	uint16_t no;
	char devname[8 + 1];

	seg = addr >> 16;
	off = addr & 0xffff;
	while(1) {
		no = real_readw(seg, off + 4);
		next_seg = real_readw(seg, off + 2);
		next_off = real_readw(seg, off);
		if(no & 0x8000) {
			for(no = 0 ; no < 8 ; no++) {
				if((devname[no] = real_readb(seg, off + 10 + no)) <= 0x20) {
					devname[no] = 0;
					break;
				}
			}
			devname[8] = 0;
			if(!strcmp(name, devname)) {
				if(already_flag) {
					for(no = 0 ; no < DOS_DEVICES ; no++) {
						if(Devices[no]) {
							if(Devices[no]->GetInformation() & DeviceInfoFlags::ExternalDevice) {
								if(((DOS_ExtDevice *)Devices[no])->CheckSameDevice(seg, real_readw(seg, off + 6), real_readw(seg, off + 8))) {
									return 0;
								}
							}
						}
					}
				}
				// Exclude the default CON and NUL
				if(real_readd(seg, off + 6) == 0 || real_readd(seg, off + 6) == 0xffffffff) {
					return 0;
				}
				return (uint32_t)seg << 16 | (uint32_t)off;
			}
		}
		if(next_seg == 0xffff && next_off == 0xffff) {
			break;
		}
		seg = next_seg;
		off = next_off;
	}
	return 0;
}

static void DOS_CheckOpenExtDevice(const char *name) {
	uint32_t addr;

	if((addr = DOS_CheckExtDevice(name, true)) != 0) {
		LOG(LOG_MISC,LOG_DEBUG)("Found external device driver '%s' (%x:%x), adding", name, addr >> 16, addr & 0xffff);
		DOS_ExtDevice *device = new DOS_ExtDevice(name, addr >> 16, addr & 0xffff);
		DOS_AddDevice(device);
	}
}

class device_NUL : public DOS_Device {
public:
	device_NUL() { SetName("NUL"); };
	bool Read(uint8_t * data,uint16_t * size) override {
        (void)data; // UNUSED
		*size = 0; //Return success and no data read.
//		LOG(LOG_IOCTL,LOG_NORMAL)("%s:READ",GetName());
		return true;
	}
	bool Write(const uint8_t * data,uint16_t * size) override {
        (void)data; // UNUSED
        (void)size; // UNUSED
//		LOG(LOG_IOCTL,LOG_NORMAL)("%s:WRITE",GetName());
		return true;
	}
	bool Seek(uint32_t * pos,uint32_t type) override {
        (void)type;
        (void)pos;
//		LOG(LOG_IOCTL,LOG_NORMAL)("%s:SEEK",GetName());
		return true;
	}
	bool Close() override { return true; }
	uint16_t GetInformation(void) override { return DeviceInfoFlags::Device | DeviceInfoFlags::Nul; }
	bool ReadFromControlChannel(PhysPt bufptr,uint16_t size,uint16_t * retcode) override { (void)bufptr; (void)size; (void)retcode; return false; }
	bool WriteToControlChannel(PhysPt bufptr,uint16_t size,uint16_t * retcode) override { (void)bufptr; (void)size; (void)retcode; return false; }
};

#if !defined(OSFREE)
class device_PRN : public DOS_Device {
public:
	device_PRN() {
		SetName("PRN");
	}
	bool Read(uint8_t * data,uint16_t * size) override {
        (void)data; // UNUSED
        (void)size; // UNUSED
		DOS_SetError(DOSERR_ACCESS_DENIED);
		return false;
	}
	bool Write(const uint8_t * data,uint16_t * size) override {
		for(int i = 0; i < 9; i++) {
			// look up a parallel port
			if(parallelPortObjects[i] != NULL) {
				// send the data
				for (uint16_t j=0; j<*size; j++) {
					if(!parallelPortObjects[i]->Putchar(data[j])) return false;
				}
				return true;
			}
		}
		return false;
	}
	bool Seek(uint32_t * pos,uint32_t type) override {
        (void)type; // UNUSED
		*pos = 0;
		return true;
	}
	uint16_t GetInformation(void) override {
		return DeviceInfoFlags::Device | DeviceInfoFlags::Binary;
	}
	bool Close() override {
		return false;
	}
};
#endif

uint16_t cpMap[512] = { // Codepage is standard 437
	0x0020, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2219, 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
	0x25ba, 0x25c4, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8, 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
	0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
	0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
	0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,		// 176 - 223 line/box drawing
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
	0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
	0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x0020,

	0x211e, 0x2120, 0x2122, 0x00ae, 0x00a9, 0x00a4, 0x2295, 0x2299, 0x2297, 0x2296, 0x2a38, 0x21d5, 0x21d4, 0x21c4, 0x2196, 0x2198,		// Second bank for WP extended charset
	0x2197, 0x2199, 0x22a4, 0x22a5, 0x22a2, 0x22a3, 0x2213, 0x2243, 0x2260, 0x2262, 0x00b3, 0x00be, 0x0153, 0x0152, 0x05d0, 0x05d1,
	0x0000, 0x2111, 0x211c, 0x2200, 0x2207, 0x29cb, 0x0394, 0x039b, 0x03a0, 0x039e, 0x03a8, 0x05d2, 0x03b3, 0x03b7, 0x03b9, 0x03ba,
	0x03bb, 0x03bd, 0x03c1, 0x03a5, 0x03c9, 0x03be, 0x03b6, 0x02bf, 0x03c5, 0x03c8, 0x03c2, 0x2113, 0x03c7, 0x222e, 0x0178, 0x00cf,
	0x00cb, 0x2227, 0x00c1, 0x0107, 0x0106, 0x01f5, 0x013a, 0x00cd, 0x0139, 0x0144, 0x0143, 0x00d3, 0x0155, 0x0154, 0x015b, 0x015a,
	0x00da, 0x00fd, 0x00dd, 0x017a, 0x0179, 0x00c0, 0x00c8, 0x00cc, 0x00d2, 0x00d9, 0x0151, 0x0150, 0x0171, 0x0170, 0x016f, 0x016e,
	0x010b, 0x010a, 0x0117, 0x0116, 0x0121, 0x0120, 0x0130, 0x017c, 0x017b, 0x00c2, 0x0109, 0x0108, 0x00ca, 0x011d, 0x011c, 0x0125,
	0x0124, 0x00ce, 0x0135, 0x0134, 0x00d4, 0x015d, 0x015c, 0x00db, 0x0175, 0x0174, 0x0177, 0x0176, 0x00e3, 0x00c3, 0x0129, 0x0128,
	0x00f5, 0x00d5, 0x0169, 0x0168, 0x0103, 0x0102, 0x011f, 0x011e, 0x016d, 0x016c, 0x010d, 0x010c, 0x010f, 0x010e, 0x011b, 0x011a,
	0x013e, 0x013d, 0x0148, 0x0147, 0x0159, 0x0158, 0x0161, 0x0160, 0x0165, 0x0164, 0x017e, 0x017d, 0x00f0, 0x0122, 0x0137, 0x0136,
	0x013c, 0x013b, 0x0146, 0x0145, 0x0157, 0x0156, 0x015f, 0x015e, 0x0163, 0x0162, 0x00df, 0x0133, 0x0132, 0x00f8, 0x00d8, 0x2218,
	0x0123, 0x0000, 0x01f4, 0x01e6, 0x2202, 0x0000, 0x0020, 0x2309, 0x2308, 0x230b, 0x230a, 0x23ab, 0x2320, 0x2321, 0x23a9, 0x23a8,
	0x23ac, 0x01e7, 0x2020, 0x2021, 0x201e, 0x222b, 0x222a, 0x2282, 0x2283, 0x2288, 0x2289, 0x2286, 0x2287, 0x220d, 0x2209, 0x2203,
	0x21d1, 0x21d3, 0x21d0, 0x21d2, 0x25a1, 0x2228, 0x22bb, 0x2234, 0x2235, 0x2237, 0x201c, 0x201d, 0x2026, 0x03b8, 0x0101, 0x0113,
	0x0100, 0x0112, 0x012b, 0x012a, 0x014d, 0x014c, 0x016b, 0x016a, 0x0105, 0x0104, 0x0119, 0x0118, 0x012f, 0x012e, 0x0173, 0x0172,
	0x0111, 0x0110, 0x0127, 0x0126, 0x0167, 0x0166, 0x0142, 0x0141, 0x0140, 0x013f, 0x00fe, 0x00de, 0x014a, 0x014b, 0x0149, 0x0000
};

uint16_t cpMap_PC98[256] = {
//  0       1       2       3       4       5       6       7
    0x0020, 0x2401, 0x2402, 0x2403, 0x2404, 0x2405, 0x2406, 0x2407,                 // 0x00-0x07   Except NUL, control chars have small letters spelling out the ASCII control code
    0x2408, 0x2409, 0x240A, 0x240B, 0x240C, 0x240D, 0x240E, 0x240F,                 // 0x08-0x0F   i.e. CR is ␍. The mapping here does not EXACTLY render as it does on real hardware.
    0x2410, 0x2411, 0x2412, 0x2413, 0x2414, 0x2415, 0x2416, 0x2417,                 // 0x10-0x17
    0x2418, 0x2419, 0x241A, 0x241B, 0x2192, 0x2190, 0x2191, 0x2193,                 // 0x18-0x1F   The last 4 are single-wide arrows
    0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,                 // 0x20-0x27
    0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,                 // 0x28-0x2F
    0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,                 // 0x30-0x37
    0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,                 // 0x38-0x3F
    0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,                 // 0x40-0x47
    0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,                 // 0x48-0x4F
    0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,                 // 0x50-0x57
    0x0058, 0x0059, 0x005A, 0x005B, 0x00A5, 0x005D, 0x005E, 0x005F,                 // 0x58-0x5F   0x5C = Yen
    0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,                 // 0x60-0x67
    0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,                 // 0x68-0x6F
    0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,                 // 0x70-0x77
    0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x0020,                 // 0x78-0x7F   0x7F = Does not display as anything
    0x2581, 0x2582, 0x2583, 0x2584, 0x2585, 0x2586, 0x2587, 0x2588,                 // 0x80-0x87   Block characters, vertical 1/8 to 8/8
    0x258F, 0x258E, 0x258D, 0x258C, 0x258B, 0x258A, 0x2589, 0x253C,                 // 0x88-0x8F   Block characters, horizontal 1/8 to 7/8
    0x2534, 0x252C, 0x2524, 0x251C, 0x0020, 0x2500, 0x2502, 0x2595,                 // 0x90-0x97
    0x250C, 0x2510, 0x2514, 0x2518, 0x256D, 0x256E, 0x2570, 0x256F,                 // 0x98-0x9F
    0x0020, 0xFF61, 0xFF62, 0xFF63, 0xFF64, 0xFF65, 0xFF66, 0xFF67,                 // 0xA0-0xA7   0xA0 = Nothing. 0xA1-0xDF = Single-wide Katakana
    0xFF68, 0xFF69, 0xFF6A, 0xFF6B, 0xFF6C, 0xFF6D, 0xFF6E, 0xFF6F,                 // 0xA8-0xAF
    0xFF70, 0xFF71, 0xFF72, 0xFF73, 0xFF74, 0xFF75, 0xFF76, 0xFF77,                 // 0xB0-0xB7
    0xFF78, 0xFF79, 0xFF7A, 0xFF7B, 0xFF7C, 0xFF7D, 0xFF7E, 0xFF7F,                 // 0xB8-0xBF
    0xFF80, 0xFF81, 0xFF82, 0xFF83, 0xFF84, 0xFF85, 0xFF86, 0xFF87,                 // 0xC0-0xC7
    0xFF88, 0xFF89, 0xFF8A, 0xFF8B, 0xFF8C, 0xFF8D, 0xFF8E, 0xFF8F,                 // 0xC8-0xCF
    0xFF90, 0xFF91, 0xFF92, 0xFF93, 0xFF94, 0xFF95, 0xFF96, 0xFF97,                 // 0xD0-0xD7
    0xFF98, 0xFF99, 0xFF9A, 0xFF9B, 0xFF9C, 0xFF9D, 0xFF9E, 0xFF9F,                 // 0xD8-0xDF
    0x2550, 0x255E, 0x256A, 0x2561, 0x0020, 0x0020, 0x0020, 0x0020,                 // 0xE0-0xE7   FIXME: E0h-E3h is an approximation. Find Unicode approx. for E4h-E7h
    0x2660, 0x2665, 0x2666, 0x2663, 0x25CF, 0x25CB, 0x2571, 0x2572,                 // 0xE8-0xEF
    0x2573, 0x0020, 0x5E74, 0x6708, 0x65E5, 0x0020, 0x5206, 0x0020,                 // 0xF0-0xF7   FIXME: What is F1h, F5h, F7h?
    0x0020, 0x0020, 0x0020, 0x0020, 0x005C, 0x0020, 0x0020, 0x0020                  // 0xF8-0xFF   0xFC = Backslash
};

#if !defined(OSFREE)
bool getClipboard();
bool lastwrite = false;
uint32_t cPointer = 0, fPointer;
extern uint32_t clipSize;
extern uint8_t *clipAscii;
class device_CLIP : public DOS_Device {
private:
	char	tmpAscii[20];
	char	tmpUnicode[20];
	std::string rawdata;				// the raw data sent to CLIP$...
	virtual void CommitData() {
		if (cPointer>0) {
			if (!clipSize)
				getClipboard();
			if (cPointer>clipSize)
				cPointer=clipSize;
			if (clipSize) {
				if (rawdata.capacity() < 100000)
					rawdata.reserve(100000);
				std::string temp=std::string((char *)clipAscii).substr(0, cPointer);
				if (temp[temp.length()-1]!='\n') temp+="\n";
				rawdata.insert(0, temp);
				clipSize=0;
			}
			cPointer=0;
		}
		FILE* fh = fopen(tmpAscii, "wb");			// Append or write to ASCII file
		if (fh)
			{
			fwrite(rawdata.c_str(), rawdata.size(), 1, fh);
			fclose(fh);
			fh = fopen(tmpUnicode, "w+b");			// The same for Unicode file (it's eventually read)
			if (fh)
				{
				char text[3];
				bool lead = false;
				uint16_t uname[4];
				uint8_t pchr = 0;
				fprintf(fh, "\xff\xfe");											// It's a Unicode text file
				for (uint32_t i = 0; i < rawdata.size(); i++)
					{
					if (lead) {
						lead = false;
						if (pchr && isKanji2(rawdata[i]&0xff)) {
							text[0]=pchr&0xff;
							text[1]=rawdata[i]&0xff;
							text[2]=0;
							uname[0]=0;
							uname[1]=0;
							if ((IS_JDOSV || dos.loaded_codepage == 932) && del_flag && (text[1] & 0xFF) == 0x7F) text[1]++;
							if (CodePageGuestToHostUTF16(uname,text)) {
								fwrite(uname, 1, 2, fh);
								continue;
							} else
								fwrite(cpMap+pchr, 1, 2, fh);
						} else
							fwrite(cpMap+pchr, 1, 2, fh);
						pchr = 0;
					} else if ((IS_PC98_ARCH && shiftjis_lead_byte(rawdata[i]&0xff)) || (isDBCSCP() && isKanji1(rawdata[i]&0xff))) {
						pchr = rawdata[i];
						lead = true;
						continue;
					}
					uint16_t textChar =  (uint8_t)rawdata[i];
					switch (textChar)
						{
					case 9:																// Tab
					case 12:															// Formfeed
						fwrite(&textChar, 1, 2, fh);
						break;
					case 10:															// Linefeed (combination)
					case 13:
#if defined(WIN32)
						fwrite("\x0d\x00\x0a\x00", 1, 4, fh);
#else
						fwrite("x0a\x00", 1, 2, fh);
#endif
						if (i < rawdata.size() -1 && textChar == 23-rawdata[i+1])
							i++;
						break;
					default:
						if (textChar >= 32)												// Forget about further control characters?
							fwrite(cpMap+textChar, 1, 2, fh);
						break;
						}
					}
				}
			}
		if (!fh)
			{
			rawdata.clear();
			return;
			}
#if defined(WIN32)
		if (OpenClipboard(NULL))
			{
			if (EmptyClipboard())
				{
				int bytes = ftell(fh);
				HGLOBAL hCbData = GlobalAlloc(0, bytes);
				uint8_t* pChData = (uint8_t*)GlobalLock(hCbData);
				if (pChData)
					{
					fseek(fh, 2, SEEK_SET);											// Skip Unicode signature
					fread(pChData, 1, bytes-2, fh);
					pChData[bytes-2] = 0;
					pChData[bytes-1] = 0;
					SetClipboardData(CF_UNICODETEXT, hCbData);
					GlobalUnlock(hCbData);
					}
				}
			CloseClipboard();
			}
#elif defined(C_SDL2) || defined(MACOSX)
            std::ifstream in(tmpAscii);
            std::string contents((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
            std::string result="";
            std::istringstream iss(contents.c_str());
            for (std::string token; std::getline(iss, token); ) {
                if (token.size() && token.back() == 13) token.pop_back();
                char* uname = CodePageGuestToHost(token.c_str());
                result+=(uname!=NULL?std::string(uname):token)+std::string(1, 10);
            }
            if (result.size()&&result.back()==10) result.pop_back();
#if defined(C_SDL2)
            SDL_SetClipboardText(result.c_str());
#else
            bool SetClipboard(std::string value);
            SetClipboard(result);
#endif
#endif
		fclose(fh);
		remove(tmpAscii);
		remove(tmpUnicode);
		rawdata.clear();
		return;
	}
public:
	device_CLIP() {
		SetName(*dos_clipboard_device_name?dos_clipboard_device_name:"CLIP$");
		strcpy(tmpAscii, "#clip$.asc");
		strcpy(tmpUnicode, "#clip$.txt");
	}
	bool Read(uint8_t * data,uint16_t * size) override {
		if(control->SecureMode()||!(dos_clipboard_device_access==2||dos_clipboard_device_access==4)) {
			*size = 0;
			return true;
		}
		lastwrite=false;
		if (!clipSize)																// If no data, we have to read the Windows CLipboard (clipSize gets reset on device close)
			{
			getClipboard();
			fPointer = 0;
			}
		if (fPointer >= clipSize)
			*size = 0;
		else if (fPointer+*size > clipSize)
			*size = (uint16_t)(clipSize-fPointer);
		if (*size > 0) {
			memmove(data, clipAscii+fPointer, *size);
			fPointer += *size;
		}
		return true;
	}
	bool Write(const uint8_t * data,uint16_t * size) override {
		if(control->SecureMode()||!(dos_clipboard_device_access==3||dos_clipboard_device_access==4)) {
			DOS_SetError(DOSERR_ACCESS_DENIED);
			return false;
		}
		lastwrite=true;
        const uint8_t* datasrc = (uint8_t*)data;
		uint8_t * datadst = (uint8_t *) data;

		int numSpaces = 0;
		for (uint16_t idx = *size; idx; idx--)
			{
			if (*datasrc == ' ')														// Put spaces on hold
				numSpaces++;
			else
				{
				if (numSpaces && *datasrc != 0x0a && *datasrc != 0x0d)					// Spaces on hold and not end of line
					while (numSpaces--)
						*(datadst++) = ' ';
				numSpaces = 0;
				*(datadst++) = *datasrc;
				}
			datasrc++;
			}
		while (numSpaces--)
			*(datadst++) = ' ';
		if (uint16_t newsize = (uint16_t)(datadst - data))									// If data
			{
			if (rawdata.capacity() < 100000)											// Prevent repetitive size allocations
				rawdata.reserve(100000);
			rawdata.append((char *)data, newsize);
			}
		return true;
	}
	bool Seek(uint32_t * pos,uint32_t type) override {
		if(control->SecureMode()||!(dos_clipboard_device_access==2||dos_clipboard_device_access==4)) {
			*pos = 0;
			return true;
		}
		lastwrite=false;
		if (clipSize == 0)																// No data yet
			{
			getClipboard();
			fPointer =0;
			}
		int32_t newPos;
		switch (type)
			{
		case 0:																			// Start of file
			newPos = *pos;
			break;
		case 1:																			// Current file position
			newPos = fPointer+*pos;
			break;
		case 2:																			// End of file
			newPos = clipSize+*pos;
			break;
		default:
			{
			DOS_SetError(DOSERR_FUNCTION_NUMBER_INVALID);
			return false;
			}
			}
		if (newPos > (int32_t)clipSize)													// Different from "real" Files
			newPos = clipSize;
		else if (newPos < 0)
			newPos = 0;
		*pos = newPos;
		cPointer = newPos;
		fPointer = newPos;
		return true;
	}
	bool Close() override {
		if(control->SecureMode()||dos_clipboard_device_access<2)
			return false;
		clipSize = 0;																	// Reset clipboard read
		rawdata.erase(rawdata.find_last_not_of(" \n\r\t")+1);							// Remove trailing white space
		if (!rawdata.size()&&!lastwrite)												// Nothing captured/to do
			return false;
		lastwrite=false;
		int len = (int)rawdata.size();
		if (len > 2 && rawdata[len-3] == 0x0c && rawdata[len-2] == 27 && rawdata[len-1] == 64)	// <ESC>@ after last FF?
			rawdata.erase(len-2, 2);
		CommitData();
		return true;
	}
	uint16_t GetInformation(void) override {
		return DeviceInfoFlags::Device | DeviceInfoFlags::EofOnInput | DeviceInfoFlags::Binary;
	}
};
#endif

bool DOS_Device::Read(uint8_t * data,uint16_t * size) {
	return Devices[devnum]->Read(data,size);
}

bool DOS_Device::Write(const uint8_t * data,uint16_t * size) {
	// Enables console capture with VZ editor resident and xscript
	if(IS_PC98_ARCH && Devices[devnum]->IsName("CON")) {
		uint16_t keep_ax = reg_ax;
		for(uint16_t n = 0 ; n < *size ; n++) {
			reg_al = *data++;
			CALLBACK_RunRealInt(0x29);
		}
		reg_ax = keep_ax;
		return true;
	}
	return Devices[devnum]->Write(data,size);
}

bool DOS_Device::Seek(uint32_t * pos,uint32_t type) {
	return Devices[devnum]->Seek(pos,type);
}

bool DOS_Device::Close() {
	return Devices[devnum]->Close();
}

uint16_t DOS_Device::GetInformation(void) {
	return Devices[devnum]->GetInformation();
}

void DOS_Device::SetInformation(uint16_t info) {
	if(Devices[devnum]->IsName("CON") && !(Devices[devnum]->GetInformation() & DeviceInfoFlags::ExternalDevice)) {
		Devices[devnum]->SetInformation(info);
	}
}

bool DOS_Device::ReadFromControlChannel(PhysPt bufptr,uint16_t size,uint16_t * retcode) {
	return Devices[devnum]->ReadFromControlChannel(bufptr,size,retcode);
}

bool DOS_Device::WriteToControlChannel(PhysPt bufptr,uint16_t size,uint16_t * retcode) {
	return Devices[devnum]->WriteToControlChannel(bufptr,size,retcode);
}

uint8_t DOS_Device::GetStatus(bool input_flag) {
	uint16_t info = Devices[devnum]->GetInformation();
	if(info & DeviceInfoFlags::ExternalDevice) {
		return Devices[devnum]->GetStatus(input_flag);
	}
	return (info & DeviceInfoFlags::EofOnInput) ? 0x00 : 0xff;
}

DOS_File::DOS_File(const DOS_File& orig) : flags(orig.flags), open(orig.open), attr(orig.attr),
time(orig.time), date(orig.date), refCtr(orig.refCtr), hdrive(orig.hdrive) {
	if(orig.name) {
		name=new char [strlen(orig.name) + 1];strcpy(name,orig.name);
	}
}

DOS_File& DOS_File::operator= (const DOS_File& orig) {
    if (this != &orig) {
        flags = orig.flags;
        time = orig.time;
        date = orig.date;
        attr = orig.attr;
        refCtr = orig.refCtr;
        open = orig.open;
        hdrive = orig.hdrive;
        drive = orig.drive;
        newtime = orig.newtime;
        if (name) {
            delete[] name; name = nullptr;
        }
        if (orig.name) {
            name = new char[strlen(orig.name) + 1]; strcpy(name, orig.name);
        }
    }
    return *this;
}

uint8_t DOS_FindDevice(char const * name) {
	/* should only check for the names before the dot and spacepadded */
	char fullname[DOS_PATHLENGTH];uint8_t drive;
	bool ime_flag = false;
//	if(!name || !(*name)) return DOS_DEVICES; //important, but makename does it

	// NTS: If a program is trying to open paths like "@:\\WINBOOT.SYS" the most likely
	//      reason is that it's a Microsoft program like FORMAT.COM and it's reading
	//      locations in the DOS kernel that are supposed to hold the drive letter of
	//      the boot drive, but carry zero instead.
	//
	//      "@:" paths are a sign the program is possibly trying to read files from
	//      the boot device but is using nonstandard means to determine that i.e.
	//      not using INT 21h AX=3305h as documented for MS-DOS 4.0 or higher.
	//      Real MS-DOS probably does not accept "@:" drive paths and neither should
	//      we.
	//
	//      The other reason for this rejection is that the code that was here to
	//      convert "@:" to normal paths did not initialize local variable "drive"
	//      and Windows 95 FORMAT.COM requesting "@:\\WINBOOT.SYS" prior to this fix
	//      sometimes caused a segfault.
	if(*name == '@' && *(name + 1) == ':') {
		LOG_MSG("DOS_FindDevice(): Rejecting path '%s'. @: paths are not valid. It may be a sign the program is attempting to locate the boot drive in an undocumented manner not supported by this emulator",name);
		return DOS_DEVICES;
		//ime_flag = true;   // FIXME: <- Why?
	} else {
		if (!DOS_MakeName(name,fullname,&drive)) return DOS_DEVICES;
	}
	char* name_part = strrchr_dbcs(fullname,'\\');
#if defined(WIN32) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))
 #if !defined(OSFREE)
    if(Network_IsNetworkResource(name))
		name_part = fullname;
	else
 #endif
#endif
	if(name_part) {
		*name_part++ = 0;
		//Check validity of leading directory.
		if(!Drives[drive]->TestDir(fullname) && !strcasecmp(name_part, "NUL")) return DOS_DEVICES; //can be invalid
	} else name_part = fullname;

	char* dot = strrchr(name_part,'.');
	if(dot) *dot = 0; //no ext checking

	DOS_CheckOpenExtDevice(name_part);
	for(int index = DOS_DEVICES - 1 ; index >= 0 ; index--) {
		if(Devices[index]) {
			if(Devices[index]->GetInformation() & DeviceInfoFlags::ExternalDevice) {
				if(WildFileCmp(name_part, Devices[index]->name)) {
					if(DOS_CheckExtDevice(name_part, false) != 0) {
						return index;
					} else {
						LOG(LOG_MISC,LOG_DEBUG)("Remove external device driver '%s', no longer in the device chain",name);
						delete Devices[index];
						Devices[index] = nullptr;
						break;
					}
				}
			} else {
				break;
			}
		}
	}
	if(ime_flag) {
		return DOS_DEVICES;
	}

	static char com[5] = { 'C','O','M','1',0 };
	static char lpt[5] = { 'L','P','T','1',0 };
	// AUX is alias for COM1 and PRN for LPT1
	// A bit of a hack. (but less than before).
	// no need for casecmp as makename returns uppercase
	if (strcmp(name_part, "AUX") == 0) name_part = com;
	if (strcmp(name_part, "PRN") == 0) name_part = lpt;

	/* loop through devices */
	for(uint8_t index = 0;index < DOS_DEVICES;index++) {
		if (Devices[index]) {
			if (WildFileCmp(name_part,Devices[index]->name)) return index;
		}
	}
	return DOS_DEVICES;
}

void DOS_AddDevice(DOS_Device * adddev) {
//Caller creates the device. We store a pointer to it
//TODO Give the Device a real handler in low memory that responds to calls
	if (adddev == NULL) E_Exit("DOS_AddDevice() with null ptr");
	for(Bitu i = 0; i < DOS_DEVICES; i++) {
		if (Devices[i] == NULL){
//			LOG_MSG("DOS_AddDevice() %s (%p)",adddev->name,(void*)adddev);
			Devices[i] = adddev;
			Devices[i]->SetDeviceNumber(i);
			return;
		}
	}
	E_Exit("DOS_AddDevice(): Too many devices added");
}

static void DelDeviceUpdateFiles(const char * deviceName) {
	for (uint8_t handle = 0; handle < DOS_FILES; handle++) {
		DOS_File* file = Files[handle];
		if (file && !strcmp(file->name, deviceName)) {
//			LOG_MSG("Closing %s (%p)",file->name,(void*)file);
			/* DOS_CloseFile() takes care of bookkeeping, including updating
			 * Files. However, we cannot allow references to remain to the
			 * device that we are about to delete. So, we force the reference
			 * counter to be 1. If we do not, DOS_Device::Close() may be called
			 * by DOS_CloseFile() for a deleted device (and crash the process). */
			file->refCtr = 1;
			if (!DOS_CloseFile(handle, true))
				LOG_MSG("WARNING: DOS_CloseFile() failed to close %s",deviceName);
		}
	}
}

void DOS_DelDevice(DOS_Device * dev) {
// We will destroy the device if we find it in our list.
	if (dev == NULL) return E_Exit("DOS_DelDevice() with null ptr");

	/* We should match names, because neither files nor devices have a proper
	 * ID. The address of neither DOS_File nor DOS_Device objects is a proper
	 * ID, because the objects can be copied. Note that device names are
	 * unique, but that separate DOS_File objects can refer to the same device.
	 * - dbjh */
	DelDeviceUpdateFiles(dev->name);

	for (Bitu i = 0; i < DOS_DEVICES; i++) {
		if (Devices[i] != NULL) { /* This code sets Devices[i] to NULL after delete on device name match so don't assume it's non-NULL! */
			if (!strcmp(Devices[i]->name, dev->name)) {
//				LOG_MSG("DOS_DelDevice() %s (%p)",dev->name,(void*)dev);
				delete Devices[i];
				Devices[i] = nullptr;
				return;
			}
		}
	}

	/* hm. unfortunately, too much code in DOSBox assumes that we delete the object.
	 * prior to this fix, failure to delete caused a memory leak */
	LOG_MSG("WARNING: DOS_DelDevice() failed to match device object '%s' (%p). Deleting anyway",dev->name,(void*)dev);
	delete dev;
}

void DOS_ShutdownDevices(void) {
	for (Bitu i=0;i < DOS_DEVICES;i++) {
		if (Devices[i] != NULL) {
//			LOG_MSG("DOS: Shutting down device %s (%p)\n",Devices[i]->name,(void*)Devices[i]);
			delete Devices[i];
			Devices[i] = NULL;
		}
	}

    /* NTS: CON counts as a device */
    if (IS_PC98_ARCH) update_pc98_function_row(0);
}

// INT 29h emulation needs to keep track of CON
device_CON *DOS_CON = NULL;

bool ANSI_SYS_installed() {
    if (DOS_CON != NULL)
        return DOS_CON->ANSI_SYS_installed();

    return false;
}

uint8_t DOS_GetAnsiAttr() {
    if (DOS_CON != NULL)
        return DOS_CON->GetAnsiAttr();
    return 0;
}

bool DOS_SetAnsiAttr(uint8_t attr) {
    if (DOS_CON != NULL) {
        DOS_CON->SetAnsiAttr(attr);
        return true;
    }
    return false;
}

void DOS_ClearKeyMap() {
	for(Bitu i = 0 ; i < DOS_DEVICES ; i++) {
		if(Devices[i]) {
			if(Devices[i]->IsName("CON")) {
				device_CON *con = (device_CON *)Devices[i];
				con->ClearKeyMap();
				break;
			}
		}
	}
}

void DOS_SetConKey(uint16_t src, uint16_t dst) {
	for(Bitu i = 0 ; i < DOS_DEVICES ; i++) {
		if(Devices[i]) {
			if(Devices[i]->IsName("CON")) {
				device_CON *con = (device_CON *)Devices[i];
				con->SetKeyMap(src, dst);
				break;
			}
		}
	}
}

void DOS_SetupDevices(void) {
	DOS_Device * newdev;
	DOS_CON=new device_CON(); newdev=DOS_CON;
	DOS_AddDevice(newdev);
	DOS_Device * newdev2;
	newdev2=new device_NUL();
	DOS_AddDevice(newdev2);
#if !defined(OSFREE)
	DOS_Device * newdev3;
	newdev3=new device_PRN();
	DOS_AddDevice(newdev3);
#endif
#if !defined(OSFREE)
	if (dos_clipboard_device_access) {
		DOS_Device * newdev4;
		newdev4=new device_CLIP();
		DOS_AddDevice(newdev4);
	}
#endif
}

/* PC-98 INT DC CL=0x10 AH=0x00 DL=cjar */
void PC98_INTDC_WriteChar(unsigned char b) {
    if (DOS_CON != NULL) {
        uint16_t sz = 1;

        DOS_CON->Write(&b,&sz);
    }
}

void INTDC_CL10h_AH03h(uint16_t raw) {
    if (DOS_CON != NULL)
        DOS_CON->INTDC_CL10h_AH03h(raw);
}

void INTDC_CL10h_AH04h(void) {
    if (DOS_CON != NULL)
        DOS_CON->INTDC_CL10h_AH04h();
}

void INTDC_CL10h_AH05h(void) {
    if (DOS_CON != NULL)
        DOS_CON->INTDC_CL10h_AH05h();
}

void INTDC_CL10h_AH06h(uint16_t count) {
    if (DOS_CON != NULL)
        DOS_CON->INTDC_CL10h_AH06h(count);
}

void INTDC_CL10h_AH07h(uint16_t count) {
    if (DOS_CON != NULL)
        DOS_CON->INTDC_CL10h_AH07h(count);
}

void INTDC_CL10h_AH08h(uint16_t count) {
    if (DOS_CON != NULL)
        DOS_CON->INTDC_CL10h_AH08h(count);
}

void INTDC_CL10h_AH09h(uint16_t count) {
    if (DOS_CON != NULL)
        DOS_CON->INTDC_CL10h_AH09h(count);
}

void INTDC_CL10h_AH0Ah(uint16_t pattern) {
    if (DOS_CON != NULL)
        DOS_CON->INTDC_CL10h_AH0Ah(pattern);
}

void INTDC_CL10h_AH0Bh(uint16_t pattern) {
    if (DOS_CON != NULL)
        DOS_CON->INTDC_CL10h_AH0Bh(pattern);
}

void INTDC_CL10h_AH0Ch(uint16_t count) {
    if (DOS_CON != NULL)
        DOS_CON->INTDC_CL10h_AH0Ch(count);
}

void INTDC_CL10h_AH0Dh(uint16_t count) {
    if (DOS_CON != NULL)
        DOS_CON->INTDC_CL10h_AH0Dh(count);
}

/* The CB_INT28 handler calls this callback then executes STI+HLT.
 * This works great when called from the CON device because on return,
 * when IRQ1 keyboard input breaks the HLT and returns to CON, this
 * keeps CPU load down. But what if you're some DOS program that
 * likes to call INT 28h a lot just to appease some multitasking OS
 * or NetWare servers? INT 28h will block until any interrupt
 * happens whether or not pending keyboard data is waiting for the
 * CON device. Usually it's the regular tick of IRQ0 that finally
 * breaks HLT, but that's only at 18.2 ticks/sec and some programs
 * like the Pacific C freeware compiler like to call INT 28h a lot
 * before processing keyboard input. So to avoid stalls in that
 * case, INT 28h will use STI+HLT only once. The CON driver will
 * set that flag every time it calls it and then set it again when
 * it completes to allow one more STI+HLT should external DOS
 * programs call INT 28h. */
bool INT28_AllowOnce = true;
Bitu INT28_HANDLER(void) {
	bool skip = false;

	if (INT28_AllowOnce) {
		INT28_AllowOnce = false;
	}
	else {
		skip = true;
	}

	if (skip) {
		/* skip STI+HLT */
		reg_ip += 2;
	}

	return CBRET_NONE;
}

Bitu INT29_HANDLER(void) {
	if (DOS_CON != NULL) {
		unsigned char b = reg_al;
		uint16_t sz = 1;

		DOS_CON->Write(&b,&sz);
	}

	return CBRET_NONE;
}

extern bool dos_kernel_disabled;

// save state support
void POD_Save_DOS_Devices( std::ostream& stream )
{
	if (!dos_kernel_disabled) {
		if( strcmp( Devices[2]->GetName(), "CON" ) == 0 )
			Devices[2]->SaveState(stream);
	}
}

void POD_Load_DOS_Devices( std::istream& stream )
{
	if (!dos_kernel_disabled) {
		if( strcmp( Devices[2]->GetName(), "CON" ) == 0 )
			Devices[2]->LoadState(stream, false);
	}
}
