////////////////////////////////////////////////////////////////////////////////
// Win32/Toolbox.cpp -- this file is part of the Emulator Developers Kit
// available at http://ourworld.compuserve.com/homepages/pc64/develop.htm
//
// The Toolbox contains classes and routines for use with Win32 projects.
//
// In order to use the toolbox with other projects:
// - add the directory of Toolbox.h to the compiler's include path
// - add the file Toolbox.cpp to your project
// - let every source file start with #include <Toolbox.h>
// - set the precompiled headers to Toolbox.h

#define Toolbox_cpp
#include "Toolbox.h"


////////////////////////////////////////////////////////////////////////////////
// define global linkage for the toolbox components
// use /D "ExportToolbox" if the code resides in a DLL

#if defined(ExportToolbox)
  #define global __declspec(dllexport)
#else
  #define global
#endif


////////////////////////////////////////////////////////////////////////////////
// global variables for the application

extern const char gacApplication[];
extern const char gacVersion[];
extern const char gacAuthor[];
extern const char gacSupport[];


////////////////////////////////////////////////////////////////////////////////
// log frame window proc

static LRESULT CALLBACK LogFrameWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  switch (uMsg) {
  case WM_SIZE:
    MoveWindow(GetWindow(hwnd, GW_CHILD), 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
    return 0;
  case WM_SETFOCUS:
    SetFocus(GetWindow(hwnd, GW_CHILD));
    return 0;
  case WM_CLOSE:
    ShowWindow(hwnd, SW_HIDE);
    return 0;
  }
  return DefWindowProc(hwnd, uMsg, wParam, lParam);
}


////////////////////////////////////////////////////////////////////////////////
// write the log header

static DWORD CALLBACK WriteLogHeaderCallback(DWORD dwCookie, byte* pbBuffer, long lSize, long* plCount) {

  // all data will be returned with the first call
  static bool bNoMoreData;
  if (bNoMoreData) {
    return 1;
  }

  // RTF settings and icon descriptions
  char* pc = (char*)pbBuffer;
  strcpy(pc,
    "{\\rtf1\\ansi\\deff0\\deftab720\\deflang1031"
    "{\\fonttbl{\\f0\\fmodern\\fprq1 Courier New;}{\\f1\\fnil\\fprq2\\fcharset2 Wingdings;}}"
    "{\\colortbl\\red0\\green0\\blue0;\\red0\\green128\\blue0;\\red0\\green0\\blue255;\\red255\\green0\\blue255;\\red255\\green0\\blue0;\\red128\\green128\\blue0;}"
    "\\qc"
    "\\cf0\\f1\\fs28\\'F0\\f0\\fs20\\~Info  "
    "\\cf1\\f1\\fs28\\'3C\\f0\\fs20\\~Trace  "
    "\\cf2\\f1\\fs28\\'37\\f0\\fs20\\~User  "
    "\\cf3\\f1\\fs28\\'B4\\f0\\fs20\\~Warning  "
    "\\cf4\\f1\\fs28\\'4D\\f0\\fs20\\~Error\\par"
    "\\cf5"
  );
  pc += strlen(pc);

  // add the text where users can get support
  strcpy(pc, gacSupport);
  pc += strlen(pc);

  // add the separator line
  strcpy(pc, "\\par\\cf0");
  pc += strlen(pc);
  for (int i = 0; i < dwCookie; i++) {
    strcpy(pc, "\\emdash");
    pc += strlen(pc);
  }

  // end of RTF text
  strcpy(pc, "\\par\\pard\\par}");
  pc += strlen(pc);

  // return size of data
  *plCount = pc - (char*)pbBuffer;

  // ensure that the buffer is large enough
  verify(lSize > *plCount);

  // return success for this call and "no more data" for future calls
  bNoMoreData = true;
  return 0;
}


////////////////////////////////////////////////////////////////////////////////
// get the log edit window

static CRITICAL_SECTION gLockLog;
global HWND GetLogEditWindow() {

  // create it if called for the first time
  static HWND hwndEdit;
  if (hwndEdit == NULL) {

    // only one thread may write to the log window
    InitializeCriticalSection(&gLockLog);

    // load the RichEdit library
    if ((int)LoadLibrary("RichEd32.dll") < 32) {
      return NULL;
    }

    // register frame window class
    WNDCLASS wc;
    memset(&wc, 0, sizeof wc);
    wc.lpfnWndProc = LogFrameWndProc;
    wc.hInstance = ghinst;
    wc.hIcon = NULL;
    wc.hCursor = NULL;
    wc.hbrBackground = NULL;
    wc.lpszClassName = "LogFrame";
    RegisterClass(&wc);

    // create the frame window
    HWND hwndFrame = CreateWindow("LogFrame", NULL, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, ghinst, NULL);
    if (hwndFrame == NULL) {
      return NULL;
    }

    // create the edit window
    hwndEdit = CreateWindow("RichEdit", NULL, WS_CHILD | WS_VISIBLE | ES_MULTILINE | WS_VSCROLL | ES_AUTOVSCROLL | ES_WANTRETURN, 0, 0, 0, 0, hwndFrame, (HMENU)1, ghinst, NULL);
    if (hwndEdit == NULL) {
      DestroyWindow(hwndFrame);
      return NULL;
    }

    // resize the frame window
    const int iCharactersPerLine = 72;
    RECT rc;
    GetWindowRect(hwndFrame, &rc);
    MoveWindow(hwndFrame, rc.left, rc.top, (iCharactersPerLine + 4) * LOWORD(GetDialogBaseUnits()) + GetSystemMetrics(SM_CXVSCROLL), rc.bottom - rc.top, true);

    // set title of the frame window
    char ac[80];
    wsprintf(ac, "%s Log", gacApplication);
    SetWindowText(hwndFrame, ac);

    // write the header to the edit window
    SendMessage(hwndEdit, EM_SETBKGNDCOLOR, 0, RGB(0xFF, 0xFF, 0xFF));
    EDITSTREAM es = { iCharactersPerLine + 2, 0, WriteLogHeaderCallback };
    SendMessage(hwndEdit, EM_STREAMIN, SF_RTF, (LONG)&es);

    // set cursor to the end of the log window
    CHARRANGE CharRange = { -1, -1 };
    SendMessage(hwndEdit, EM_EXSETSEL, 0, (LPARAM)&CharRange);
  }
  return hwndEdit;
}


////////////////////////////////////////////////////////////////////////////////
// add a string to the log window

static char* gpcLastError;
static void Log(int iType, const char** ppcFormat) {

  // get text string
  char ac[4096];
  char* pcEnd = ac + wvsprintf(ac, *ppcFormat, (va_list)(&*ppcFormat + 1));
  assert(pcEnd - ac < sizeof ac);

  // ignore the error if it is identical to the last one
  bool bIsError = iType == 5;
  if (bIsError) {
    if (gpcLastError != NULL && strcmp(ac, gpcLastError) == 0) {
      return;
    }
    free(gpcLastError);
    gpcLastError = strdup(ac);
  } else {
    free(gpcLastError);
    gpcLastError = NULL;
  }

  // get the log window
  HWND hwnd = GetLogEditWindow();
  if (hwnd == NULL) {
    return;
  }

  // don't let other treads write to the log window
  EnterCriticalSection(&gLockLog);

  // set cursor to the end of the log window
  CHARRANGE CharRange = { -1, -1 };
  SendMessage(hwnd, EM_EXSETSEL, 0, (LPARAM)&CharRange);

  // indent paragraph
  PARAFORMAT pfm;
  pfm.cbSize = sizeof pfm;
  pfm.dwMask = PFM_ALIGNMENT | PFM_NUMBERING | PFM_STARTINDENT | PFM_OFFSET | PFM_RIGHTINDENT;
  pfm.wNumbering = 0;
  pfm.dxStartIndent = 0;
  pfm.dxRightIndent = 0;
  pfm.dxOffset = 360;
  pfm.wAlignment = PFA_LEFT;
  SendMessage(hwnd, EM_SETPARAFORMAT, 0, (LPARAM)&pfm);

  // set color
  static COLORREF acrColor[6] = {
    RGB(0x00, 0x00, 0x00),
    RGB(0x00, 0x00, 0x00),
    RGB(0x00, 0x80, 0x00),
    RGB(0x00, 0x00, 0xFF),
    RGB(0xFF, 0x00, 0xFF),
    RGB(0xFF, 0x00, 0x00)
  };
  if (iType != 0) {
    assert(iType < 6);
    acrColor[0] = acrColor[iType];
  }
  CHARFORMAT cfm;
  cfm.cbSize = sizeof cfm;
  cfm.dwMask = CFM_COLOR | CFM_BOLD | CFM_ITALIC | CFM_STRIKEOUT | CFM_UNDERLINE | CFM_PROTECTED | CFM_SIZE | CFM_OFFSET | CFM_CHARSET | CFM_FACE;
  cfm.dwEffects = 0;
  cfm.yOffset = 0;
  cfm.crTextColor = acrColor[0];

  // output the icon in Wingdings 14 pt
  if (iType != 0) {
    cfm.yHeight = 280;
    cfm.bCharSet = SYMBOL_CHARSET;
    cfm.bPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
    strcpy(cfm.szFaceName, "Wingdings");
    SendMessage(hwnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cfm);
    static const word awIcon[] = { 0x20, 0xF0, 0x3C, 0x37, 0xB4, 0x4D };
    SendMessage(hwnd, EM_REPLACESEL, 0, (LPARAM)&awIcon[iType]);
  }

  // switch to Courier New 10 pt
  cfm.yHeight = 200;
  cfm.bCharSet = DEFAULT_CHARSET;
  cfm.bPitchAndFamily = FIXED_PITCH | FF_MODERN;
  strcpy(cfm.szFaceName, "Courier New");
  SendMessage(hwnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cfm);

  // output the text and add a tab after each newline
  char* pcStart = ac;
  do {
    SendMessage(hwnd, EM_REPLACESEL, 0, (LPARAM)"\t");
    char* pcNewLine = (char*)memchr(pcStart, '\n', pcEnd - pcStart);
    if (pcNewLine == NULL) {
      pcNewLine = pcEnd;
    }
    pcNewLine[0] = 0;
    if (pcNewLine > pcStart && pcNewLine[-1] == '\r') {
      pcNewLine[-1] = 0;
    }
    SendMessage(hwnd, EM_REPLACESEL, 0, (LPARAM)pcStart);
    SendMessage(hwnd, EM_REPLACESEL, 0, (LPARAM)"\n");
    pcStart = pcNewLine + 1;
  } while (pcStart < pcEnd);

  // other treads may now write to the log window again
  LeaveCriticalSection(&gLockLog);
}


////////////////////////////////////////////////////////////////////////////////
// inline is not possible for functions with variable args

global void __cdecl more(const char* pcFormat, ...) {
  Log(0, &pcFormat);
}
global void __cdecl info(const char* pcFormat, ...) {
  Log(1, &pcFormat);
}
global void __cdecl trace(const char* pcFormat, ...) {
  Log(2, &pcFormat);
}
global void __cdecl user(const char* pcFormat, ...) {
  Log(3, &pcFormat);
}
global void __cdecl warning(const char* pcFormat, ...) {
  Log(4, &pcFormat);
}
global void __cdecl _Error(const char* pcFormat, ...) {
  Log(5, &pcFormat);
  throw gpcLastError;
}


////////////////////////////////////////////////////////////////////////////////
// pop up the log window or display a message box if log not available

global void report() {
  HWND hwnd = GetLogEditWindow();
  if (hwnd != NULL) {
    SendMessage(hwnd, EM_SCROLLCARET, 0, 0);
    hwnd = GetParent(hwnd);
    ShowWindow(hwnd, SW_SHOWNORMAL);
    MSG msg;
    while (GetForegroundWindow() == hwnd && GetMessage(&msg, NULL, 0, 0)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  } else {
    if (gpcLastError != NULL) {
      MessageBox(GetActiveWindow(), gpcLastError, gacApplication, MB_ICONEXCLAMATION | MB_OK);
    } else {
      MessageBox(GetActiveWindow(), "System error with unknown reason", gacApplication, MB_ICONEXCLAMATION | MB_OK);
    }
  }
}


////////////////////////////////////////////////////////////////////////////////
// error in a waveIn/waveOut function

global void _WaveError(const char* pcStatement, const char* pcFile, int iLine, MMRESULT mmrError, MMRESULT (WINAPI* waveGetErrorText)(MMRESULT, LPSTR, UINT)) {
  char ac[MAXERRORLENGTH];
  char* pcError = "";
  if (waveGetErrorText(mmrError, ac, sizeof ac) == MMSYSERR_NOERROR) {
    pcError = ac;
  }
  _Error("%s failed in %s at line %d with error number %d:\n%s", pcStatement, GetFileName(pcFile), iLine, mmrError, pcError);
}


////////////////////////////////////////////////////////////////////////////////
// get error string from Windows

global char* WinErrorString(DWORD dwError) {
  static char ac[MAXERRORLENGTH];
  int iLength = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), ac, sizeof ac, NULL);
  if (iLength == 0) {
    wsprintf(ac, "(Unknown system error number %d, cannot retrieve error text)", dwError);
  }
  assert(strlen(ac) < sizeof ac);
  return ac;
}


////////////////////////////////////////////////////////////////////////////////
// Windows error

global void _WinError(const char* pcFunction, const char* pcFile, int iLine) {
  DWORD dwError = GetLastError();
  _Error("%s failed in %s at line %d with error 0x%08X\n%s", pcFunction, GetFileName(pcFile), iLine, dwError, WinErrorString(dwError));
}


////////////////////////////////////////////////////////////////////////////////
// check platform

global void _InitGOSVI() {
  gosvi.dwOSVersionInfoSize = sizeof gosvi;
  win(GetVersionEx(&gosvi));
  if (gosvi.dwPlatformId == VER_PLATFORM_WIN32s) {
    SetErrorMode(SEM_NOOPENFILEERRORBOX);
  }
}


////////////////////////////////////////////////////////////////////////////////
// swap two ranges of memory

global void swap(void* p1, void* p2, int iSize) {
  byte abSaved1[1024];
  int iCount = sizeof abSaved1;
  do {
    if (iCount > iSize) {
      iCount = iSize;
    }
    memcpy(abSaved1, p1, iCount);
    memcpy(p1, p2, iCount);
    memcpy(p2, abSaved1, iCount);
    iSize -= iCount;
  } while (iSize > 0);
  assert(iSize == 0);
}


////////////////////////////////////////////////////////////////////////////////
// direct memory and port access
//
// The driver DirectNT.sys is Copyright (C)1997 Matthias Withopf, Andreas
// Stiller and has been published in the German c't magazine 1'97 on page 312.
// It can be download from ftp://ftp.heise.de/pub/ct as ct9701.zip.

#define DIRECTNT_TYPE 40000
#define IOCTL_DIRECTNT_CONTROL (DWORD)CTL_CODE(DIRECTNT_TYPE, 0x0800, METHOD_BUFFERED, FILE_READ_ACCESS)
#define OP_Check             1 // returns $12345678
#define OP_GetCR0            2 // reads CR0
#define OP_SetCR0            3 // writes CR0
#define OP_ReadPortByte     10 // reads port dwPar1 (IO permission not necessary)
#define OP_WritePortByte    11 // writes dwPar2 to port dwPar1
#define OP_GetMR            20 // reads MSR dwPar1, return value is 64 bit
#define OP_SetMR            21 // writes MSR dwPar1, dwPar2/dwPar3 = 64 bit low/high
#define OP_ReadMemDword     30 // linear memory read
#define	OP_ReadPhysMemDword 40 // physical memory read
#define OP_GiveIO           50 // allows direct IO to ports dwPar1..dwPar2
#define OP_LoadIOPM         51 // loads IO permission map from dwPar1 (pointer to 8 KB)
#define OP_LockIO           52 // forbids direct IO to ports dwPar1..dwPar2
#define OP_ReadPCIDword     60 // reads PCI register dwPar1 with BUS/DEV/FKN in dwPar2
#define OP_WritePCIDword    61 // writes dwPar3 to PCI register dwPar1 with BUS/DEV/FKN in dwPar2
#define OP_ReadFlags        99 // internal test function, query driver flags

typedef struct {
  DWORD dwOpCode;
  DWORD dwPar1;
  DWORD dwPar2;
  DWORD dwPar3;
} DirectNTInfo;

static HANDLE ghDirectNT;

static void ConnectToDirectNT() {
  ghDirectNT = CreateFile("\\\\.\\Dev_DirectNT", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
  if (ghDirectNT == INVALID_HANDLE_VALUE) {
    error("DirectNT.sys not running! Cannot access LPT ports. Please re-install PC64Win");
  }
}

global int GetLPTPort(int iLPT) {
  assert(iLPT >= 1 && iLPT <= 4);
  if (IsWindowsNT()) {
    if (ghDirectNT == NULL) {
      ConnectToDirectNT();
    }
    DirectNTInfo dnti = { OP_ReadPhysMemDword, 0x00000408 + (iLPT - 1) * 2 };
    DWORD dwResult = 0;
    DWORD dwResultLen;
    win(DeviceIoControl(ghDirectNT, IOCTL_DIRECTNT_CONTROL, &dnti, sizeof dnti, &dwResult, sizeof dwResult, &dwResultLen, NULL));
    assert(dwResultLen == sizeof dwResult);
    return dwResult & 0xFFFF;
  } else if (IsWin32s()) {
    int i = 0x0008 + (iLPT - 1) * 2;
    __asm {
      push ES
      mov AX,0x0040
      mov ES,AX
      mov EAX,i
      mov EAX,ES:[EAX]
      pop ES
      mov i,EAX
    }
    return i & 0xFFFF;
  } else {
    word wResult;
    DWORD dwResultLen;
    win(ReadProcessMemory(GetCurrentProcess(), (void*)(0x00000408 + (iLPT - 1) * 2), &wResult, sizeof wResult, &dwResultLen));
    assert(dwResultLen == sizeof wResult);
    return wResult;
  }
}

global int inp2(int iPort) {
  if (IsWindowsNT()) {
    if (ghDirectNT == NULL) {
      ConnectToDirectNT();
    }
    DirectNTInfo dnti = { OP_ReadPortByte, iPort };
    DWORD dwResult = 0;
    DWORD dwResultLen;
    win(DeviceIoControl(ghDirectNT, IOCTL_DIRECTNT_CONTROL, &dnti, sizeof dnti, &dwResult, 1, &dwResultLen, NULL));
    assert(dwResultLen == 1);
    return (int)dwResult;
  } else {
    int iOld;
    int iNew = _inp(iPort);
    do {
      iOld = iNew;
      iNew = _inp(iPort);
    } while (iNew != iOld);
    return iNew;
  }
}

global void outp(int iPort, int iValue) {
  if (IsWindowsNT()) {
    if (ghDirectNT == NULL) {
      ConnectToDirectNT();
    }
    DirectNTInfo dnti = { OP_WritePortByte, iPort, iValue };
    ULONG dwResultLen;
    win(DeviceIoControl(ghDirectNT, IOCTL_DIRECTNT_CONTROL, &dnti, sizeof dnti, NULL, 0, &dwResultLen, NULL));
  } else {
    _outp(iPort, iValue);
  }
}


////////////////////////////////////////////////////////////////////////////////
// allocate and free memory aligned to 32 byte cache lines

global byte* MemAlloc(size_t Size) {
  byte* pbOrigin = (byte*)malloc(Size + 32 + 4);
  if (pbOrigin == NULL) {
    error("not enough memory");
  }
  byte* pb = (byte*)((int)pbOrigin + 31 + 4 & ~31);
  assert((unsigned)(pb - pbOrigin) <= 32);
  ((byte**)pb)[-1] = pbOrigin;
  return pb;
}

global void MemFree(void* pMem) {
  if (pMem != NULL) {
    assert(((int)pMem & 31) == 0);
    byte* pbOrigin = ((byte**)pMem)[-1];
    assert(pbOrigin != NULL);
    assert((unsigned)((byte*)pMem - pbOrigin) <= 32);
    ((byte**)pMem)[-1] = NULL;
    free(pbOrigin);
  }
}


////////////////////////////////////////////////////////////////////////////////
// disk functions

global const char* GetProgramDirectory(const char* pcFileName) {
  static char acBuffer[_MAX_PATH];
  static char* pcSlash;
  static char* pcCopy;
  if (acBuffer[0] == 0) {
    win(GetModuleFileName(NULL, acBuffer, sizeof acBuffer));
    assert(acBuffer[0] != 0);
    assert(strlen(acBuffer) < sizeof acBuffer);
    pcSlash = strrchr(acBuffer, '\\');
    assert(pcSlash != NULL);
    *pcSlash = 0;
    char* pcEDK = strrchr(acBuffer, '\\');
    if (pcEDK != NULL && stricmp(pcEDK, "\\EDK") == 0) {
      pcSlash = pcEDK;
    }
    *pcSlash = '\\';
    pcCopy = pcSlash + 1;
    if (pcSlash > acBuffer && strchr(":\\/", pcSlash[-1]) != NULL) {
      pcSlash++;
    }
  }
  if (pcFileName != NULL) {
    *pcSlash = '\\';
    strcpy(pcCopy, pcFileName);
    assert(strlen(acBuffer) < sizeof acBuffer);
  } else {
    *pcSlash = 0;
  }
  return acBuffer;
}

global void AddPath(char* pcDest, const char* pcSrc) {
  assert(pcSrc != NULL);
  if (*pcSrc != 0) {
    assert(*pcSrc != '\\');
    int iLength = strlen(pcDest);
    char* pc = pcDest + iLength;
    if (iLength > 0 && strchr(":\\/", pc[-1]) == NULL) {
      *pc++ = '\\';
    }
    strcpy(pc, pcSrc);
  }
}

global char* GetFileName(const char* pcPath) {
  assert(pcPath != NULL);
  char* pcSlash = strrchr(pcPath, '\\');
  if (pcSlash != NULL) {
    return pcSlash + 1;
  } else {
    return (char*)pcPath;
  }
}

global double GetDiskFreeSpace(char cDrive) {
  char ac[4] = { cDrive, ':', '\\', 0 };
  DWORD dwSectorsPerCluster;
  DWORD dwBytesPerSector;
  DWORD dwNumberOfFreeClusters;
  DWORD dwTotalNumberOfClusters;
  if (GetDiskFreeSpace(ac, &dwSectorsPerCluster, &dwBytesPerSector, &dwNumberOfFreeClusters, &dwTotalNumberOfClusters)) {
    return (double)dwBytesPerSector * (double)dwSectorsPerCluster * (double)dwNumberOfFreeClusters;
  } else {
    return 0;
  }
}


////////////////////////////////////////////////////////////////////////////////
// window functions

global void CenterWindow(HWND hwnd) {
  RECT rcWindow;
  GetWindowRect(hwnd, &rcWindow);
  RECT rcScreen;
  SystemParametersInfo(SPI_GETWORKAREA, NULL, &rcScreen, NULL);
  int iX = rcScreen.left + ((rcScreen.right - rcScreen.left) - (rcWindow.right - rcWindow.left)) / 2;
  int iY = rcScreen.top + ((rcScreen.bottom - rcScreen.top) - (rcWindow.bottom - rcWindow.top)) / 2;
	SetWindowPos(hwnd, NULL, iX, iY, -1, -1, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}

global void SetDlgItemHex(HWND hwnd, int iItem, long lValue, int iLength) {
  assert(iLength >= 0);
  assert(iLength <= 8);
  char ac[9];
  wsprintf(ac, "%08lX", lValue);
  SetDlgItemText(hwnd, iItem, ac + 8 - iLength);
}

global long GetDlgItemHex(HWND hwnd, int iItem) {
  char ac[40];
  GetDlgItemText (hwnd, iItem, ac, 40);
  return strtol(ac, NULL, 16);
}


////////////////////////////////////////////////////////////////////////////////
// let the user input a string

static const char* gpcInputPrompt;
static char* gpcInputBuffer;
static int iInputBufferSize;

static BOOL CALLBACK InputDlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  try {
    switch (uMsg) {
    case WM_INITDIALOG:
      assert(gpcInputBuffer != NULL);
      CenterWindow(hwnd);
      SetWindowText(hwnd, gacApplication);
      SetDlgItemText(hwnd, 100, gpcInputPrompt);
      SetDlgItemText(hwnd, 101, gpcInputBuffer);
      SendDlgItemMessage(hwnd, 101, EM_LIMITTEXT, iInputBufferSize - 1, 0);
      return true;
    case WM_COMMAND:
      switch (LOWORD(wParam)) {
      case IDOK:
        GetDlgItemText(hwnd, 101, gpcInputBuffer, iInputBufferSize);
      case IDCANCEL:
        EndDialog(hwnd, LOWORD(wParam));
        return true;
      }
    }
  } catch (...) {
    report();
  }
  return FALSE;
}

global bool Input(const char* pcPrompt, char* pcBuffer, int iSize) {
  static byte abTemplate[] = {
    0xC0,0x00,0xC8,0x80,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0xB1,0x00,
    0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x4D,0x00,0x53,0x00,0x20,0x00,
    0x53,0x00,0x61,0x00,0x6E,0x00,0x73,0x00,0x20,0x00,0x53,0x00,0x65,0x00,0x72,0x00,
    0x69,0x00,0x66,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x83,0x50,0x00,0x00,0x00,0x00,
    0x05,0x00,0x0F,0x00,0xA5,0x00,0x0E,0x00,0x65,0x00,0xFF,0xFF,0x81,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x80,0x00,0x02,0x50,0x00,0x00,0x00,0x00,0x05,0x00,0x05,0x00,
    0xA5,0x00,0x08,0x00,0x64,0x00,0xFF,0xFF,0x82,0x00,0x53,0x00,0x74,0x00,0x61,0x00,
    0x74,0x00,0x69,0x00,0x63,0x00,0x00,0x00,0x00,0x00,0x00,0x00
  };
  gpcInputPrompt = pcPrompt;
  gpcInputBuffer = pcBuffer;
  iInputBufferSize = iSize;
  int iReturnCode = DialogBoxIndirect(ghinst, (DLGTEMPLATE*)abTemplate, ghwnd, InputDlgProc);
  gpcInputBuffer = NULL;
  return iReturnCode == IDOK;
}


////////////////////////////////////////////////////////////////////////////////
// include the component sources which reside in separate files

#include "File.cpp"
#include "MappedFile.cpp"
#include "Config.cpp"
