VOID EmulatorException(BYTE ExceptionNumber, LPWORD Stack) { WORD CodeSegment, InstructionPointer; PBYTE Opcode; ASSERT(ExceptionNumber < 8); /* Get the CS:IP */ InstructionPointer = Stack[STACK_IP]; CodeSegment = Stack[STACK_CS]; Opcode = (PBYTE)SEG_OFF_TO_PTR(CodeSegment, InstructionPointer); /* Display a message to the user */ DisplayMessage(L"Exception: %s occured at %04X:%04X\n" L"Opcode: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", ExceptionName[ExceptionNumber], CodeSegment, InstructionPointer, Opcode[0], Opcode[1], Opcode[2], Opcode[3], Opcode[4], Opcode[5], Opcode[6], Opcode[7], Opcode[8], Opcode[9]); /* Stop the VDM */ EmulatorTerminate(); return; }
static VOID WINAPI DosSystemBop(LPWORD Stack) { /* Get the Function Number and skip it */ BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP()); setIP(getIP() + 1); switch (FuncNum) { case 0x11: // Load the DOS kernel { BOOLEAN Success = FALSE; HANDLE hDosKernel; ULONG ulDosKernelSize = 0; DPRINT1("You are loading Windows NT DOS!\n"); /* Open the DOS kernel file */ hDosKernel = FileOpen("ntdos.sys", &ulDosKernelSize); /* If we failed, bail out */ if (hDosKernel == NULL) goto Quit; /* * Attempt to load the DOS kernel into memory. * The segment where to load the DOS kernel is defined * by the DOS BIOS and is found in DI:0000 . */ Success = FileLoadByHandle(hDosKernel, REAL_TO_PHYS(TO_LINEAR(getDI(), 0x0000)), ulDosKernelSize, &ulDosKernelSize); DPRINT1("Windows NT DOS loading %s at 0x%04X:0x%04X, size 0x%x ; GetLastError() = %u\n", (Success ? "succeeded" : "failed"), getDI(), 0x0000, ulDosKernelSize, GetLastError()); /* Close the DOS kernel file */ FileClose(hDosKernel); Quit: if (!Success) { /* We failed everything, stop the VDM */ EmulatorTerminate(); } break; } default: { DPRINT1("Unknown DOS System BOP Function: 0x%02X\n", FuncNum); // setCF(1); // Disable, otherwise we enter an infinite loop break; } } }
VOID WINAPI VDDTerminateVDM(VOID) { /* Stop the VDM */ EmulatorTerminate(); }
VOID MenuEventHandler(PMENU_EVENT_RECORD MenuEvent) { switch (MenuEvent->dwCommandId) { case ID_SHOWHIDE_MOUSE: ShowHideMousePointer(ConsoleOutput, ShowPointer); ShowPointer = !ShowPointer; break; case ID_VDM_DUMPMEM_TXT: DumpMemory(TRUE); break; case ID_VDM_DUMPMEM_BIN: DumpMemory(FALSE); break; case ID_VDM_QUIT: /* Stop the VDM */ EmulatorTerminate(); break; default: break; } }
static BOOL WINAPI ConsoleCtrlHandler(DWORD ControlType) { switch (ControlType) { case CTRL_C_EVENT: case CTRL_BREAK_EVENT: { /* HACK: Stop the VDM */ DPRINT1("Ctrl-C/Break: Stop the VDM\n"); EmulatorTerminate(); break; } case CTRL_LAST_CLOSE_EVENT: { if (WaitForSingleObject(VdmTaskEvent, 0) == WAIT_TIMEOUT) { /* Exit immediately */ #ifndef STANDALONE if (CommandThread) TerminateThread(CommandThread, 0); #endif EmulatorTerminate(); } #ifndef STANDALONE else { /* Stop accepting new commands */ AcceptCommands = FALSE; } #endif break; } default: { /* Stop the VDM if the user logs out or closes the console */ EmulatorTerminate(); } } return TRUE; }
static VOID DemLoadNTDOSKernel(VOID) { BOOLEAN Success = FALSE; LPCSTR DosKernelFileName = "ntdos.sys"; HANDLE hDosKernel; ULONG ulDosKernelSize = 0; DPRINT1("You are loading Windows NT DOS!\n"); /* Open the DOS kernel file */ hDosKernel = FileOpen(DosKernelFileName, &ulDosKernelSize); if (hDosKernel == NULL) goto Quit; /* * Attempt to load the DOS kernel into memory. * The segment where to load the DOS kernel is defined * by the DOS BIOS and is found in DI:0000 . */ Success = FileLoadByHandle(hDosKernel, REAL_TO_PHYS(TO_LINEAR(getDI(), 0x0000)), ulDosKernelSize, &ulDosKernelSize); DPRINT1("Windows NT DOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n", DosKernelFileName, (Success ? "succeeded" : "failed"), getDI(), 0x0000, ulDosKernelSize, GetLastError()); /* Close the DOS kernel file */ FileClose(hDosKernel); Quit: if (!Success) { /* We failed everything, stop the VDM */ BiosDisplayMessage("Windows NT DOS kernel file '%s' loading failed (Error: %u). The VDM will shut down.\n", DosKernelFileName, GetLastError()); EmulatorTerminate(); return; } }
VOID CpuSimulate(VOID) { if (CpuCallLevel > MaxCpuCallLevel) { DisplayMessage(L"Too many CPU levels of recursion (%d, expected maximum %d)", CpuCallLevel, MaxCpuCallLevel); /* Stop the VDM */ EmulatorTerminate(); return; } CpuCallLevel++; DPRINT("CpuSimulate --> Level %d\n", CpuCallLevel); CpuRunning = TRUE; while (VdmRunning && CpuRunning) ClockUpdate(); DPRINT("CpuSimulate <-- Level %d\n", CpuCallLevel); CpuCallLevel--; if (!VdmRunning || CpuCallLevel < 0) CpuCallLevel = 0; /* This takes into account for reentrance */ if (VdmRunning && (CpuCallLevel > 0)) CpuRunning = TRUE; }
static VOID WINAPI DosCmdInterpreterBop(LPWORD Stack) { /* Get the Function Number and skip it */ BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP()); setIP(getIP() + 1); switch (FuncNum) { /* Kill the VDM */ case 0x00: { /* Stop the VDM */ EmulatorTerminate(); return; } /* * Get a new app to start * * Input * DS:DX : Data block. * * Output * CF : 0: Success; 1: Failure. */ case 0x01: { CmdStartProcess(); break; } /* * Check binary format * * Input * DS:DX : Program to check. * * Output * CF : 0: Success; 1: Failure. * AX : Error code. */ case 0x07: { DWORD BinaryType; LPSTR ProgramName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX()); if (!GetBinaryTypeA(ProgramName, &BinaryType)) { /* An error happened, bail out */ setCF(1); setAX(LOWORD(GetLastError())); break; } // FIXME: We only support DOS binaries for now... ASSERT(BinaryType == SCS_DOS_BINARY); if (BinaryType != SCS_DOS_BINARY) { /* An error happened, bail out */ setCF(1); setAX(LOWORD(ERROR_BAD_EXE_FORMAT)); break; } /* Return success: DOS application */ setCF(0); break; } /* * Start an external command * * Input * DS:SI : Command to start. * ES : Environment block segment. * AL : Current drive number. * AH : 0: Directly start the command; * 1: Use "cmd.exe /c" to start the command. * * Output * CF : 0: Shell-out; 1: Continue. * AL : Error/Exit code. */ case 0x08: { CmdStartExternalCommand(); break; } /* * Start the default 32-bit command interpreter (COMSPEC) * * Input * ES : Environment block segment. * AL : Current drive number. * * Output * CF : 0: Shell-out; 1: Continue. * AL : Error/Exit code. */ case 0x0A: { CmdStartComSpec32(); break; } /* * Set exit code * * Input * DX : Exit code * * Output * CF : 0: Shell-out; 1: Continue. */ case 0x0B: { CmdSetExitCode(); break; } /* * Get start information * * Output * AL : 0 (resp. 1): Started from (resp. without) an existing console. */ case 0x10: { #ifndef STANDALONE /* * When a new instance of our (internal) COMMAND.COM is started, * we check whether we need to run a 32-bit COMSPEC. This goes by * checking whether we were started in a new console (no parent * console process) or from an existing one. * * However COMMAND.COM can also be started in the case where a * 32-bit process (started by a 16-bit parent) wants to start a new * 16-bit process: to ensure DOS reentry we need to start a new * instance of COMMAND.COM. On Windows the COMMAND.COM is started * just before the 32-bit process (in fact, it is this COMMAND.COM * which starts the 32-bit process via an undocumented command-line * switch '/z', which syntax is: * COMMAND.COM /z\bAPPNAME.EXE * notice the '\b' character inserted in-between. Then COMMAND.COM * issues a BOP_CMD 08h with AH=00h to start the process). * * Instead, we do the reverse, i.e. we start the 32-bit process, * and *only* if needed, i.e. if this process wants to start a * new 16-bit process, we start our COMMAND.COM. * * The problem we then face is that our COMMAND.COM will possibly * want to start a new COMSPEC, however we do not want this. * The chosen solution is to flag this case -- done with the 'Reentry' * boolean -- so that COMMAND.COM will not attempt to start COMSPEC * but instead will directly try to start the 16-bit process. */ // setAL(SessionId != 0); setAL((SessionId != 0) && !Reentry); /* Reset 'Reentry' */ Reentry = FALSE; #else setAL(0); #endif break; } default: { DPRINT1("Unknown DOS CMD Interpreter BOP Function: 0x%02X\n", FuncNum); // setCF(1); // Disable, otherwise we enter an infinite loop break; } } }
static VOID WINAPI DosStart(LPWORD Stack) { BOOLEAN Success; DWORD Result; #ifndef STANDALONE INT i; #endif DPRINT("DosStart\n"); /* * We succeeded, deregister the DOS Starting BOP * so that no app will be able to call us back. */ RegisterBop(BOP_START_DOS, NULL); /* Initialize the callback context */ InitializeContext(&DosContext, BIOS_CODE_SEGMENT, 0x0010); Success = DosBIOSInitialize(); // Success &= DosKRNLInitialize(); if (!Success) { BiosDisplayMessage("DOS32 loading failed (Error: %u). The VDM will shut down.\n", GetLastError()); EmulatorTerminate(); return; } /* Load the mouse driver */ DosMouseInitialize(); #ifndef STANDALONE /* Parse the command line arguments */ for (i = 1; i < NtVdmArgc; i++) { if (wcsncmp(NtVdmArgv[i], L"-i", 2) == 0) { /* This is the session ID (hex format) */ SessionId = wcstoul(NtVdmArgv[i] + 2, NULL, 16); } } /* Initialize Win32-VDM environment */ Env = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, EnvSize); if (Env == NULL) { DosDisplayMessage("Failed to initialize the global environment (Error: %u). The VDM will shut down.\n", GetLastError()); EmulatorTerminate(); return; } /* Clear the structure */ RtlZeroMemory(&CommandInfo, sizeof(CommandInfo)); /* Get the initial information */ CommandInfo.TaskId = SessionId; CommandInfo.VDMState = VDM_GET_FIRST_COMMAND | VDM_FLAG_DOS; GetNextVDMCommand(&CommandInfo); #else /* Retrieve the command to start */ if (NtVdmArgc >= 2) { WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[1], -1, AppName, sizeof(AppName), NULL, NULL); if (NtVdmArgc >= 3) WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[2], -1, CmdLine, sizeof(CmdLine), NULL, NULL); else strcpy(CmdLine, ""); } else { DosDisplayMessage("Invalid DOS command line\n"); EmulatorTerminate(); return; } #endif /* * At this point, CS:IP points to the DOS BIOS exit code. If the * root command interpreter fails to start (or if it exits), DOS * exits and the VDM terminates. */ /* Start the root command interpreter */ // TODO: Really interpret the 'SHELL=' line of CONFIG.NT, and use it! /* * Prepare the stack for DosStartComSpec: * push Flags, CS and IP, and an extra WORD. */ setSP(getSP() - sizeof(WORD)); *((LPWORD)SEG_OFF_TO_PTR(getSS(), getSP())) = (WORD)getEFLAGS(); setSP(getSP() - sizeof(WORD)); *((LPWORD)SEG_OFF_TO_PTR(getSS(), getSP())) = getCS(); setSP(getSP() - sizeof(WORD)); *((LPWORD)SEG_OFF_TO_PTR(getSS(), getSP())) = getIP(); setSP(getSP() - sizeof(WORD)); Result = DosStartComSpec(TRUE, SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0), MAKELONG(getIP(), getCS()), #ifndef STANDALONE &RootCmd.ComSpecPsp #else NULL #endif ); if (Result != ERROR_SUCCESS) { /* Unprepare the stack for DosStartComSpec */ setSP(getSP() + 4*sizeof(WORD)); DosDisplayMessage("Failed to start the Command Interpreter (Error: %u). The VDM will shut down.\n", Result); EmulatorTerminate(); return; } #ifndef STANDALONE RootCmd.Terminated = FALSE; InsertComSpecInfo(&RootCmd); #endif /**/ /* Attach to the console and resume the VM */ DosProcessConsoleAttach(); EmulatorResume(); /**/ return; }
VOID WINAPI ThirdPartyVDDBop(LPWORD Stack) { /* Get the Function Number and skip it */ BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP()); setIP(getIP() + 1); switch (FuncNum) { /* RegisterModule */ case 0: { BOOL Success = TRUE; WORD RetVal = 0; WORD Entry = 0; LPCSTR DllName = NULL, InitRoutineName = NULL, DispatchRoutineName = NULL; HMODULE hDll = NULL; VDD_PROC InitRoutine = NULL, DispatchRoutine = NULL; DPRINT("RegisterModule() called\n"); /* Clear the Carry Flag (no error happened so far) */ setCF(0); /* Retrieve the next free entry in the table (used later on) */ Entry = GetNextFreeVDDEntry(); if (Entry >= MAX_VDD_MODULES) { DPRINT1("Failed to create a new VDD module entry\n"); Success = FALSE; RetVal = 4; goto Quit; } /* Retrieve the VDD name in DS:SI */ DllName = (LPCSTR)SEG_OFF_TO_PTR(getDS(), getSI()); /* Retrieve the initialization routine API name in ES:DI (optional --> ES=DI=0) */ if (TO_LINEAR(getES(), getDI()) != 0) InitRoutineName = (LPCSTR)SEG_OFF_TO_PTR(getES(), getDI()); /* Retrieve the dispatch routine API name in DS:BX */ DispatchRoutineName = (LPCSTR)SEG_OFF_TO_PTR(getDS(), getBX()); DPRINT1("DllName = '%s' - InitRoutineName = '%s' - DispatchRoutineName = '%s'\n", (DllName ? DllName : "n/a"), (InitRoutineName ? InitRoutineName : "n/a"), (DispatchRoutineName ? DispatchRoutineName : "n/a")); /* Load the VDD DLL */ hDll = LoadLibraryA(DllName); if (hDll == NULL) { DWORD LastError = GetLastError(); Success = FALSE; if (LastError == ERROR_NOT_ENOUGH_MEMORY) { DPRINT1("Not enough memory to load DLL '%s'\n", DllName); RetVal = 4; goto Quit; } else { DPRINT1("Failed to load DLL '%s'; last error = %d\n", DllName, LastError); RetVal = 1; goto Quit; } } /* Load the initialization routine if needed */ if (InitRoutineName) { InitRoutine = (VDD_PROC)GetProcAddress(hDll, InitRoutineName); if (InitRoutine == NULL) { DPRINT1("Failed to load the initialization routine '%s'\n", InitRoutineName); Success = FALSE; RetVal = 3; goto Quit; } } /* Load the dispatch routine */ DispatchRoutine = (VDD_PROC)GetProcAddress(hDll, DispatchRoutineName); if (DispatchRoutine == NULL) { DPRINT1("Failed to load the dispatch routine '%s'\n", DispatchRoutineName); Success = FALSE; RetVal = 2; goto Quit; } /* If we arrived there, that means everything is OK */ /* Register the VDD DLL */ VDDList[Entry].hDll = hDll; VDDList[Entry].DispatchRoutine = DispatchRoutine; /* Call the initialization routine if needed */ if (InitRoutine) InitRoutine(); /* We succeeded. RetVal will contain a valid VDD DLL handle */ Success = TRUE; RetVal = ENTRY_TO_HANDLE(Entry); // Convert the entry to a valid handle Quit: if (!Success) { /* Unload the VDD DLL */ if (hDll) FreeLibrary(hDll); /* Set the Carry Flag to indicate that an error happened */ setCF(1); } // else // { // /* Clear the Carry Flag (success) */ // setCF(0); // } setAX(RetVal); break; } /* UnRegisterModule */ case 1: { WORD Handle = getAX(); WORD Entry = HANDLE_TO_ENTRY(Handle); // Convert the handle to a valid entry DPRINT("UnRegisterModule() called\n"); /* Sanity checks */ if (!IS_VALID_HANDLE(Handle) || VDDList[Entry].hDll == NULL) { DPRINT1("Invalid VDD DLL Handle: %d\n", Entry); /* Stop the VDM */ EmulatorTerminate(); return; } /* Unregister the VDD DLL */ FreeLibrary(VDDList[Entry].hDll); VDDList[Entry].hDll = NULL; VDDList[Entry].DispatchRoutine = NULL; break; } /* DispatchCall */ case 2: { WORD Handle = getAX(); WORD Entry = HANDLE_TO_ENTRY(Handle); // Convert the handle to a valid entry DPRINT("DispatchCall() called\n"); /* Sanity checks */ if (!IS_VALID_HANDLE(Handle) || VDDList[Entry].hDll == NULL || VDDList[Entry].DispatchRoutine == NULL) { DPRINT1("Invalid VDD DLL Handle: %d\n", Entry); /* Stop the VDM */ EmulatorTerminate(); return; } /* Call the dispatch routine */ VDDList[Entry].DispatchRoutine(); break; } default: { DPRINT1("Unknown 3rd-party VDD BOP Function: 0x%02X\n", FuncNum); setCF(1); break; } } }
static VOID WINAPI DosStart(LPWORD Stack) { #ifdef STANDALONE DWORD Result; CHAR ApplicationName[MAX_PATH]; CHAR CommandLine[DOS_CMDLINE_LENGTH]; #endif DPRINT("DosStart\n"); /* * We succeeded, deregister the DOS Starting BOP * so that no app will be able to call us back. */ RegisterBop(BOP_START_DOS, NULL); /* Load the mouse driver */ DosMouseInitialize(); #ifndef STANDALONE /* Create the GetNextVDMCommand thread */ CommandThread = CreateThread(NULL, 0, &CommandThreadProc, NULL, 0, NULL); if (CommandThread == NULL) { wprintf(L"FATAL: Failed to create the command processing thread: %d\n", GetLastError()); goto Quit; } /* Wait for the command thread to exit */ WaitForSingleObject(CommandThread, INFINITE); /* Close the thread handle */ CloseHandle(CommandThread); #else if (NtVdmArgc >= 2) { WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[1], -1, ApplicationName, sizeof(ApplicationName), NULL, NULL); if (NtVdmArgc >= 3) WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[2], -1, CommandLine, sizeof(CommandLine), NULL, NULL); else strcpy(CommandLine, ""); } else { DisplayMessage(L"Invalid DOS command line\n"); goto Quit; } /* Start the process from the command line */ DPRINT1("Starting '%s' ('%s')...\n", ApplicationName, CommandLine); Result = DosStartProcess(ApplicationName, CommandLine, SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0)); if (Result != ERROR_SUCCESS) { DisplayMessage(L"Could not start '%S'. Error: %u", ApplicationName, Result); goto Quit; } #endif Quit: /* Stop the VDM */ EmulatorTerminate(); }
static VOID WINAPI PS2WritePort(ULONG Port, BYTE Data) { if (Port == PS2_CONTROL_PORT) { switch (Data) { /* Read configuration byte */ case 0x20: { OutputBuffer = ControllerConfig; StatusRegister |= (1 << 0); // There is something to read break; } /* Write configuration byte */ case 0x60: /* Write controller output port */ case 0xD1: /* Write to the first PS/2 port output buffer */ case 0xD2: /* Write to the second PS/2 port output buffer */ case 0xD3: /* Write to the second PS/2 port input buffer */ case 0xD4: { /* These commands require a response */ ControllerCommand = Data; StatusRegister |= (1 << 3); // This is a controller command break; } /* Disable second PS/2 port */ case 0xA7: { Ports[1].IsEnabled = FALSE; break; } /* Enable second PS/2 port */ case 0xA8: { Ports[1].IsEnabled = TRUE; break; } /* Test second PS/2 port */ case 0xA9: { OutputBuffer = 0x00; // Success code StatusRegister |= (1 << 0); // There is something to read break; } /* Test PS/2 controller */ case 0xAA: { OutputBuffer = 0x55; // Success code StatusRegister |= (1 << 0); // There is something to read break; } /* Test first PS/2 port */ case 0xAB: { OutputBuffer = 0x00; // Success code StatusRegister |= (1 << 0); // There is something to read break; } /* Disable first PS/2 port */ case 0xAD: { Ports[0].IsEnabled = FALSE; break; } /* Enable first PS/2 port */ case 0xAE: { Ports[0].IsEnabled = TRUE; break; } /* Read controller output port */ case 0xD0: { // TODO: Not implemented break; } /* CPU Reset */ case 0xF0: case 0xF2: case 0xF4: case 0xF6: case 0xF8: case 0xFA: case 0xFC: case 0xFE: { /* Stop the VDM */ EmulatorTerminate(); break; } } } else if (Port == PS2_DATA_PORT) { /* Check if the controller is waiting for a response */ if (StatusRegister & (1 << 3)) // If we have data for the controller { StatusRegister &= ~(1 << 3); /* Check which command it was */ switch (ControllerCommand) { /* Write configuration byte */ case 0x60: { ControllerConfig = Data; break; } /* Write controller output */ case 0xD1: { /* Check if bit 0 is unset */ if (!(Data & (1 << 0))) { /* CPU disabled - Stop the VDM */ EmulatorTerminate(); } /* Update the A20 line setting */ EmulatorSetA20(Data & (1 << 1)); break; } /* Push the data byte into the first PS/2 port queue */ case 0xD2: { PS2QueuePush(0, Data); break; } /* Push the data byte into the second PS/2 port queue */ case 0xD3: { PS2QueuePush(1, Data); break; } /* * Send a command to the second PS/2 port (by default * it is a command for the first PS/2 port) */ case 0xD4: { PS2SendCommand(&Ports[1], Data); break; } } return; } /* By default, send a command to the first PS/2 port */ PS2SendCommand(&Ports[0], Data); } }