mirror of
https://github.com/reactos/reactos.git
synced 2025-08-03 04:36:26 +00:00
[NTVDM]
- Add a basic boot sequence functionality (read from CMOS); will be improved in the future. - Print a "FATAL BOOT FAILURE" error message when INT 18h is called. - Fail startup if we cannot mount the available hard disk images. - Improve some diagnostic error messages. svn path=/trunk/; revision=69421
This commit is contained in:
parent
b1b7020854
commit
59136514da
8 changed files with 216 additions and 154 deletions
|
@ -161,6 +161,31 @@ static const BYTE PostCode[] =
|
|||
|
||||
/* PRIVATE FUNCTIONS **********************************************************/
|
||||
|
||||
static VOID BiosCharPrint(CHAR Character)
|
||||
{
|
||||
/* Save AX and BX */
|
||||
USHORT AX = getAX();
|
||||
USHORT BX = getBX();
|
||||
|
||||
/*
|
||||
* Set the parameters:
|
||||
* AL contains the character to print,
|
||||
* BL contains the character attribute,
|
||||
* BH contains the video page to use.
|
||||
*/
|
||||
setAL(Character);
|
||||
setBL(DEFAULT_ATTRIBUTE);
|
||||
setBH(Bda->VideoPage);
|
||||
|
||||
/* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
|
||||
setAH(0x0E);
|
||||
Int32Call(&BiosContext, BIOS_VIDEO_INTERRUPT);
|
||||
|
||||
/* Restore AX and BX */
|
||||
setBX(BX);
|
||||
setAX(AX);
|
||||
}
|
||||
|
||||
static VOID WINAPI BiosException(LPWORD Stack)
|
||||
{
|
||||
/* Get the exception number and call the emulator API */
|
||||
|
@ -505,6 +530,8 @@ static VOID WINAPI BiosMiscService(LPWORD Stack)
|
|||
|
||||
static VOID WINAPI BiosRomBasic(LPWORD Stack)
|
||||
{
|
||||
PrintMessageAnsi(BiosCharPrint, "FATAL: INT18: BOOT FAILURE.");
|
||||
|
||||
/* ROM Basic is unsupported, display a message to the user */
|
||||
DisplayMessage(L"NTVDM doesn't support ROM Basic. The VDM is closing.");
|
||||
|
||||
|
@ -518,79 +545,110 @@ extern VOID WINAPI BiosDiskService(LPWORD Stack);
|
|||
|
||||
static VOID WINAPI BiosBootstrapLoader(LPWORD Stack)
|
||||
{
|
||||
USHORT BootOrder;
|
||||
|
||||
USHORT AX, BX, CX, DX, ES;
|
||||
AX = getAX();
|
||||
BX = getBX();
|
||||
CX = getCX();
|
||||
DX = getDX();
|
||||
ES = getES();
|
||||
|
||||
/*
|
||||
* In real BIOSes one loads the bootsector read from a diskette
|
||||
* or from a disk, copy it to 0000:7C00 and then boot it.
|
||||
* Since we are 32-bit VM and we hardcode our DOS at the moment,
|
||||
* just call the DOS 32-bit initialization code.
|
||||
* Read the boot sequence order from the CMOS, old behaviour AMI-style.
|
||||
*
|
||||
* For more information, see:
|
||||
* http://www.virtualbox.org/svn/vbox/trunk/src/VBox/Devices/PC/BIOS/orgs.asm
|
||||
* http://www.virtualbox.org/svn/vbox/trunk/src/VBox/Devices/PC/BIOS/boot.c
|
||||
* http://bochs.sourceforge.net/cgi-bin/lxr/source/iodev/cmos.cc
|
||||
* https://web.archive.org/web/20111209041013/http://www-ivs.cs.uni-magdeburg.de/~zbrog/asm/cmos.html
|
||||
* http://www.bioscentral.com/misc/cmosmap.htm
|
||||
*/
|
||||
IOWriteB(CMOS_ADDRESS_PORT, CMOS_REG_SYSOP);
|
||||
BootOrder = (IOReadB(CMOS_DATA_PORT) & 0x20) >> 5;
|
||||
|
||||
/*
|
||||
* BootOrder =
|
||||
* 0: Hard Disk, then Floppy Disk
|
||||
* 1: Floppy Disk, then Hard Disk
|
||||
* In all cases, if booting from those devices failed,
|
||||
* ROM DOS-32 is started. If it fails, INT 18h is called.
|
||||
*/
|
||||
|
||||
DPRINT("BiosBootstrapLoader -->\n");
|
||||
DPRINT("BiosBootstrapLoader (BootOrder = 0x%02X) -->\n", BootOrder);
|
||||
|
||||
/*
|
||||
* Format of the BootOrder command:
|
||||
* 2 bytes. Each half-byte contains the ID of the drive to boot.
|
||||
* Currently defined:
|
||||
* 0x0: 1st Floppy disk
|
||||
* 0x1: 1st Hard disk
|
||||
* Other, or 0xF: Stop boot sequence.
|
||||
*/
|
||||
BootOrder = 0xFF00 | ((1 << (4 * BootOrder)) & 0xFF);
|
||||
|
||||
Retry:
|
||||
switch (BootOrder & 0x0F)
|
||||
{
|
||||
USHORT i;
|
||||
|
||||
USHORT AX, BX, CX, DX, ES;
|
||||
AX = getAX();
|
||||
BX = getBX();
|
||||
CX = getCX();
|
||||
DX = getDX();
|
||||
ES = getES();
|
||||
|
||||
// i = 0;
|
||||
i = 1;
|
||||
do
|
||||
/* Boot from 1st floppy drive */
|
||||
case 0:
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
/* Boot from 1st floppy drive */
|
||||
setAH(0x02); // Read sectors
|
||||
setAL(0x01); // Number of sectors
|
||||
setDH(0x00); // Head 0
|
||||
setCH(0x00); // Cylinder 0
|
||||
setCL(0x01); // Sector 1
|
||||
setDL(0x00); // First diskette drive (used by loader code, so should not be cleared)
|
||||
setES(0x0000); // Write data in 0000:7C00
|
||||
setBX(0x7C00);
|
||||
BiosDiskService(Stack);
|
||||
if (!(Stack[STACK_FLAGS] & EMULATOR_FLAG_CF)) goto Quit;
|
||||
DPRINT1("An error happened while loading the bootsector from floppy 0, error = %d\n", getAH());
|
||||
|
||||
setAH(0x02); // Read sectors
|
||||
setAL(0x01); // Number of sectors
|
||||
setDH(0x00); // First head
|
||||
setCH(0x00); // First cylinder
|
||||
setCL(0x01); // First sector
|
||||
setDL(0x00); // First diskette drive (used by loader code, so should not be cleared)
|
||||
setES(0x0000); // Place data in 0000:7C00
|
||||
setBX(0x7C00);
|
||||
BiosDiskService(Stack);
|
||||
if (!(Stack[STACK_FLAGS] & EMULATOR_FLAG_CF)) goto Quit;
|
||||
DPRINT1("An error happened while loading the bootsector from floppy 0, error = %d\n", getAH());
|
||||
}
|
||||
else if (i == 1)
|
||||
{
|
||||
/* Boot from 1st HDD drive */
|
||||
break;
|
||||
}
|
||||
|
||||
setAH(0x02); // Read sectors
|
||||
setAL(0x01); // Number of sectors
|
||||
setDH(0x00); // First head
|
||||
setCH(0x00); // First cylinder
|
||||
setCL(0x01); // First sector
|
||||
setDL(0x80); // First HDD drive (used by loader code, so should not be cleared)
|
||||
setES(0x0000); // Place data in 0000:7C00
|
||||
setBX(0x7C00);
|
||||
BiosDiskService(Stack);
|
||||
if (!(Stack[STACK_FLAGS] & EMULATOR_FLAG_CF)) goto Quit;
|
||||
DPRINT1("An error happened while loading the bootsector from HDD 0, error = %d\n", getAH());
|
||||
}
|
||||
// } while (i++ < 1);
|
||||
} while (i-- > 0);
|
||||
/* Boot from 1st HDD drive */
|
||||
case 1:
|
||||
{
|
||||
setAH(0x02); // Read sectors
|
||||
setAL(0x01); // Number of sectors
|
||||
setDH(0x00); // Head 0
|
||||
setCH(0x00); // Cylinder 0
|
||||
setCL(0x01); // Sector 1
|
||||
setDL(0x80); // First HDD drive (used by loader code, so should not be cleared)
|
||||
setES(0x0000); // Write data in 0000:7C00
|
||||
setBX(0x7C00);
|
||||
BiosDiskService(Stack);
|
||||
if (!(Stack[STACK_FLAGS] & EMULATOR_FLAG_CF)) goto Quit;
|
||||
DPRINT1("An error happened while loading the bootsector from HDD 0, error = %d\n", getAH());
|
||||
|
||||
/* Clear everything, we are going to load DOS32 */
|
||||
setAX(AX);
|
||||
setBX(BX);
|
||||
setCX(CX);
|
||||
setDX(DX);
|
||||
setES(ES);
|
||||
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
goto StartDos;
|
||||
}
|
||||
|
||||
/* Go to next drive and invalidate the last half-byte. */
|
||||
BootOrder = (BootOrder >> 4) | 0xF000;
|
||||
goto Retry;
|
||||
|
||||
StartDos:
|
||||
/* Clear everything, we are going to load DOS32 */
|
||||
setAX(AX);
|
||||
setBX(BX);
|
||||
setCX(CX);
|
||||
setDX(DX);
|
||||
setES(ES);
|
||||
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
||||
|
||||
/* Load our DOS */
|
||||
DosBootsectorInitialize();
|
||||
|
||||
Quit:
|
||||
/*
|
||||
* Position CPU to 0000:7C00 to boot the OS.
|
||||
* Jump to 0000:7C00 to boot the OS.
|
||||
*
|
||||
* Since we are called via the INT32 mechanism, we need to correctly set
|
||||
* CS:IP, not by changing the current one (otherwise the interrupt could
|
||||
|
|
|
@ -82,83 +82,6 @@ VOID DosCharPrint(CHAR Character)
|
|||
DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function, derived from ntvdm.c!DisplayMessage, is used by the BIOS and
|
||||
* the DOS to display messages to an output device. A printer function is given
|
||||
* for printing the characters.
|
||||
*/
|
||||
static VOID
|
||||
DisplayMessageAnsiV(IN CHAR_PRINT CharPrint,
|
||||
IN LPCSTR Format,
|
||||
IN va_list args)
|
||||
{
|
||||
static CHAR CurChar = 0;
|
||||
LPSTR str;
|
||||
|
||||
#ifndef WIN2K_COMPLIANT
|
||||
CHAR StaticBuffer[256];
|
||||
LPSTR Buffer = StaticBuffer; // Use the static buffer by default.
|
||||
#else
|
||||
CHAR Buffer[2048]; // Large enough. If not, increase it by hand.
|
||||
#endif
|
||||
size_t MsgLen;
|
||||
|
||||
#ifndef WIN2K_COMPLIANT
|
||||
/*
|
||||
* Retrieve the message length and if it is too long, allocate
|
||||
* an auxiliary buffer; otherwise use the static buffer.
|
||||
* The string is built to be NULL-terminated.
|
||||
*/
|
||||
MsgLen = _vscprintf(Format, args);
|
||||
if (MsgLen >= ARRAYSIZE(StaticBuffer))
|
||||
{
|
||||
Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(CHAR));
|
||||
if (Buffer == NULL)
|
||||
{
|
||||
/* Allocation failed, use the static buffer and display a suitable error message */
|
||||
Buffer = StaticBuffer;
|
||||
Format = "DisplayMessageAnsi()\nOriginal message is too long and allocating an auxiliary buffer failed.";
|
||||
MsgLen = strlen(Format);
|
||||
}
|
||||
}
|
||||
#else
|
||||
MsgLen = ARRAYSIZE(Buffer) - 1;
|
||||
#endif
|
||||
|
||||
RtlZeroMemory(Buffer, (MsgLen + 1) * sizeof(CHAR));
|
||||
_vsnprintf(Buffer, MsgLen, Format, args);
|
||||
|
||||
/* Display the message */
|
||||
DPRINT1("\n\nNTVDM DOS32\n%s\n\n", Buffer);
|
||||
|
||||
MsgLen = strlen(Buffer);
|
||||
str = Buffer;
|
||||
while (MsgLen--)
|
||||
{
|
||||
if (*str == '\n' && CurChar != '\r')
|
||||
CharPrint('\r');
|
||||
|
||||
CurChar = *str++;
|
||||
CharPrint(CurChar);
|
||||
}
|
||||
|
||||
#ifndef WIN2K_COMPLIANT
|
||||
/* Free the buffer if needed */
|
||||
if (Buffer != StaticBuffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
VOID
|
||||
DemDisplayMessage(IN CHAR_PRINT CharPrint,
|
||||
IN LPCSTR Format, ...)
|
||||
{
|
||||
va_list Parameters;
|
||||
|
||||
va_start(Parameters, Format);
|
||||
DisplayMessageAnsiV(CharPrint, Format, Parameters);
|
||||
va_end(Parameters);
|
||||
}
|
||||
|
||||
|
||||
static VOID DemLoadNTDOSKernel(VOID)
|
||||
{
|
||||
|
|
|
@ -29,18 +29,13 @@
|
|||
|
||||
/* FUNCTIONS ******************************************************************/
|
||||
|
||||
typedef VOID (*CHAR_PRINT)(IN CHAR Character);
|
||||
VOID BiosCharPrint(CHAR Character);
|
||||
VOID DosCharPrint(CHAR Character);
|
||||
|
||||
VOID DemDisplayMessage(IN CHAR_PRINT CharPrint,
|
||||
IN LPCSTR Format, ...);
|
||||
|
||||
#define BiosDisplayMessage(Format, ...) \
|
||||
DemDisplayMessage(BiosCharPrint, (Format), ##__VA_ARGS__)
|
||||
PrintMessageAnsi(BiosCharPrint, (Format), ##__VA_ARGS__)
|
||||
|
||||
VOID DosCharPrint(CHAR Character);
|
||||
#define DosDisplayMessage(Format, ...) \
|
||||
DemDisplayMessage(DosCharPrint, (Format), ##__VA_ARGS__)
|
||||
PrintMessageAnsi(DosCharPrint, (Format), ##__VA_ARGS__)
|
||||
|
||||
|
||||
BOOLEAN DosShutdown(BOOLEAN Immediate);
|
||||
|
|
|
@ -564,14 +564,22 @@ BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
|
|||
}
|
||||
}
|
||||
|
||||
/* Mount the available hard disks */
|
||||
/*
|
||||
* Mount the available hard disks. Contrary to floppies, failing
|
||||
* mounting a hard disk is considered as an unrecoverable error.
|
||||
*/
|
||||
for (i = 0; i < ARRAYSIZE(GlobalSettings.HardDisks); ++i)
|
||||
{
|
||||
if (GlobalSettings.HardDisks[i].Length != 0 &&
|
||||
GlobalSettings.HardDisks[i].Buffer &&
|
||||
GlobalSettings.HardDisks[i].Buffer != '\0')
|
||||
{
|
||||
MountDisk(HARD_DISK, i, GlobalSettings.HardDisks[i].Buffer, FALSE);
|
||||
if (!MountDisk(HARD_DISK, i, GlobalSettings.HardDisks[i].Buffer, FALSE))
|
||||
{
|
||||
wprintf(L"FATAL: Failed to mount hard disk file '%Z'.\n", &GlobalSettings);
|
||||
EmulatorCleanup();
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ typedef enum _CMOS_REGISTERS
|
|||
CMOS_REG_BASE_MEMORY_HIGH = 0x16,
|
||||
CMOS_REG_EXT_MEMORY_LOW = 0x17,
|
||||
CMOS_REG_EXT_MEMORY_HIGH = 0x18,
|
||||
CMOS_REG_SYSOP = 0x2D,
|
||||
CMOS_REG_ACTUAL_EXT_MEMORY_LOW = 0x30,
|
||||
CMOS_REG_ACTUAL_EXT_MEMORY_HIGH = 0x31,
|
||||
CMOS_REG_CENTURY = 0x32,
|
||||
|
|
|
@ -199,7 +199,7 @@ MountFDI(IN PDISK_IMAGE DiskImage,
|
|||
if (FileSize == INVALID_FILE_SIZE && GetLastError() != ERROR_SUCCESS)
|
||||
{
|
||||
/* We failed, bail out */
|
||||
DisplayMessage(L"Error when retrieving file size, or size too large (%d).", FileSize);
|
||||
DisplayMessage(L"MountFDI: Error when retrieving file size, or size too large (%d).", FileSize);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
@ -547,7 +547,11 @@ MountDisk(IN DISK_TYPE DiskType,
|
|||
FileName, hFile != INVALID_HANDLE_VALUE ? "succeeded" : "failed", GetLastError());
|
||||
|
||||
/* If we failed, bail out */
|
||||
if (hFile == INVALID_HANDLE_VALUE) return FALSE;
|
||||
if (hFile == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DisplayMessage(L"MountDisk: Error when opening disk file '%S' (Error: %u).", FileName, GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* OK, we have a handle to the file */
|
||||
|
||||
|
@ -563,7 +567,7 @@ MountDisk(IN DISK_TYPE DiskType,
|
|||
GetLastError() == ERROR_INVALID_FUNCTION)
|
||||
{
|
||||
/* Objects other than real files are not supported */
|
||||
DisplayMessage(L"'%S' is not a valid disk file.", FileName);
|
||||
DisplayMessage(L"MountDisk: '%S' is not a valid disk file.", FileName);
|
||||
goto Quit;
|
||||
}
|
||||
SetLastError(0);
|
||||
|
@ -571,7 +575,7 @@ MountDisk(IN DISK_TYPE DiskType,
|
|||
GetLastError() == ERROR_INVALID_FUNCTION)
|
||||
{
|
||||
/* Objects other than real files are not supported */
|
||||
DisplayMessage(L"'%S' is not a valid disk file.", FileName);
|
||||
DisplayMessage(L"MountDisk: '%S' is not a valid disk file.", FileName);
|
||||
goto Quit;
|
||||
}
|
||||
|
||||
|
|
|
@ -562,9 +562,9 @@ DisplayMessage(IN LPCWSTR Format, ...)
|
|||
WCHAR Buffer[2048]; // Large enough. If not, increase it by hand.
|
||||
#endif
|
||||
size_t MsgLen;
|
||||
va_list Parameters;
|
||||
va_list args;
|
||||
|
||||
va_start(Parameters, Format);
|
||||
va_start(args, Format);
|
||||
|
||||
#ifndef WIN2K_COMPLIANT
|
||||
/*
|
||||
|
@ -572,7 +572,7 @@ DisplayMessage(IN LPCWSTR Format, ...)
|
|||
* an auxiliary buffer; otherwise use the static buffer.
|
||||
* The string is built to be NULL-terminated.
|
||||
*/
|
||||
MsgLen = _vscwprintf(Format, Parameters);
|
||||
MsgLen = _vscwprintf(Format, args);
|
||||
if (MsgLen >= ARRAYSIZE(StaticBuffer))
|
||||
{
|
||||
Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(WCHAR));
|
||||
|
@ -589,9 +589,9 @@ DisplayMessage(IN LPCWSTR Format, ...)
|
|||
#endif
|
||||
|
||||
RtlZeroMemory(Buffer, (MsgLen + 1) * sizeof(WCHAR));
|
||||
_vsnwprintf(Buffer, MsgLen, Format, Parameters);
|
||||
_vsnwprintf(Buffer, MsgLen, Format, args);
|
||||
|
||||
va_end(Parameters);
|
||||
va_end(args);
|
||||
|
||||
/* Display the message */
|
||||
DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer);
|
||||
|
@ -603,6 +603,76 @@ DisplayMessage(IN LPCWSTR Format, ...)
|
|||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* This function, derived from DisplayMessage, is used by the BIOS and
|
||||
* the DOS to display messages to an output device. A printer function
|
||||
* is given for printing the characters.
|
||||
*/
|
||||
VOID
|
||||
PrintMessageAnsi(IN CHAR_PRINT CharPrint,
|
||||
IN LPCSTR Format, ...)
|
||||
{
|
||||
static CHAR CurChar = 0;
|
||||
LPSTR str;
|
||||
|
||||
#ifndef WIN2K_COMPLIANT
|
||||
CHAR StaticBuffer[256];
|
||||
LPSTR Buffer = StaticBuffer; // Use the static buffer by default.
|
||||
#else
|
||||
CHAR Buffer[2048]; // Large enough. If not, increase it by hand.
|
||||
#endif
|
||||
size_t MsgLen;
|
||||
va_list args;
|
||||
|
||||
va_start(args, Format);
|
||||
|
||||
#ifndef WIN2K_COMPLIANT
|
||||
/*
|
||||
* Retrieve the message length and if it is too long, allocate
|
||||
* an auxiliary buffer; otherwise use the static buffer.
|
||||
* The string is built to be NULL-terminated.
|
||||
*/
|
||||
MsgLen = _vscprintf(Format, args);
|
||||
if (MsgLen >= ARRAYSIZE(StaticBuffer))
|
||||
{
|
||||
Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(CHAR));
|
||||
if (Buffer == NULL)
|
||||
{
|
||||
/* Allocation failed, use the static buffer and display a suitable error message */
|
||||
Buffer = StaticBuffer;
|
||||
Format = "DisplayMessageAnsi()\nOriginal message is too long and allocating an auxiliary buffer failed.";
|
||||
MsgLen = strlen(Format);
|
||||
}
|
||||
}
|
||||
#else
|
||||
MsgLen = ARRAYSIZE(Buffer) - 1;
|
||||
#endif
|
||||
|
||||
RtlZeroMemory(Buffer, (MsgLen + 1) * sizeof(CHAR));
|
||||
_vsnprintf(Buffer, MsgLen, Format, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
/* Display the message */
|
||||
// DPRINT1("\n\nNTVDM DOS32\n%s\n\n", Buffer);
|
||||
|
||||
MsgLen = strlen(Buffer);
|
||||
str = Buffer;
|
||||
while (MsgLen--)
|
||||
{
|
||||
if (*str == '\n' && CurChar != '\r')
|
||||
CharPrint('\r');
|
||||
|
||||
CurChar = *str++;
|
||||
CharPrint(CurChar);
|
||||
}
|
||||
|
||||
#ifndef WIN2K_COMPLIANT
|
||||
/* Free the buffer if needed */
|
||||
if (Buffer != StaticBuffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
static VOID
|
||||
ConsoleCleanup(VOID);
|
||||
|
||||
|
@ -877,7 +947,7 @@ wmain(INT argc, WCHAR *argv[])
|
|||
}
|
||||
#endif
|
||||
|
||||
/* Load global VDM settings */
|
||||
/* Load the global VDM settings */
|
||||
LoadGlobalSettings(&GlobalSettings);
|
||||
|
||||
DPRINT1("\n\n\nNTVDM - Starting...\n\n\n");
|
||||
|
|
|
@ -94,7 +94,10 @@ extern HWND hConsoleWnd;
|
|||
/*
|
||||
* Interface functions
|
||||
*/
|
||||
typedef VOID (*CHAR_PRINT)(IN CHAR Character);
|
||||
VOID DisplayMessage(IN LPCWSTR Format, ...);
|
||||
VOID PrintMessageAnsi(IN CHAR_PRINT CharPrint,
|
||||
IN LPCSTR Format, ...);
|
||||
|
||||
/*static*/ VOID
|
||||
CreateVdmMenu(HANDLE ConOutHandle);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue