/* -*-C-*-
 * win32trm.c -- Module to provide Win32 API compliant graphics routines
 *
 *	Copyright (C) 1996 by the Regents of the University of California
 *
 *      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 3 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, see <https://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <windows.h>
#include <time.h>
#include <string.h>
#include <math.h>

#include "logo.h"
#include "globals.h"
#include "win32trm.h"
#include "ucbwlogo/resource.h"

// #define WIN32_S

LRESULT CALLBACK ParentWindowFunc(HWND, UINT, WPARAM, LPARAM);

char szWinName[] = "UCBLogo";
char szGraphWinName[] = "UCBLogoGraph";
char szConWinName[] = "UCBLogoConsole";

char *LogoPlatformName="Windows";

pen_info turtlePen;

HBRUSH hbrush, textbrush;

HBITMAP hbitG, hbitmG, hbitC, hbitmC;
RECT allRect;

HDC GraphDC, memGraphDC, ConDC, memConDC;
HWND hGraphWnd, hConWnd, hMainWnd;

/* color and palette related information */
static COLORREF palette[266];
FIXNUM back_ground, pen_color, old_pen_color;

int maxX, maxY, greeted = 0;
int pre_turtle_pen_mode;
long Xsofar, Ysofar;
int w_button = 0;
FIXNUM mouse_x = 0, mouse_y = 0;

BOOLEAN in_erase_mode, update_pos, seen_once = FALSE;

int in_graphics_mode, in_splitscreen;

static char **win32_lines; // for recording what should appear in the text win
static char input_lines[2][200];

char *read_line, buffered_char;
int cur_line = 0, cur_index = 0, read_index, hpos = 0, margin, cur_len;
int char_mode = 0, input_line = 0, input_index = 0;

int line_avail = FALSE, char_avail = FALSE;
char *winPasteText = NULL, *winPastePtr = NULL;
char *myScreen = NULL;
int maxXChar, maxYChar;

/* Default font behavior set here: OEM_FIXED_FONT, ANSI_FIXED_FONT, or default */
BOOLEAN oemfont = FALSE;
BOOLEAN ansifont = TRUE;

#ifdef WIN32_DEBUG
void WinDebug(char *s) {
    ndprintf("%t", s);
}
#endif

int win32_screen_right(void) {
    RECT r;

    GetClientRect(hMainWnd, &r);
    return (int) r.right;
}

int win32_screen_bottom(void) {
    RECT r;

    GetClientRect(hMainWnd, &r);
    return (int) r.bottom;
}

void moveto(int x, int y) {
    /* no invalidation of the rectangle, etc needs to be done. */
    turtlePen.h = x;
    turtlePen.v = y;
    MoveCursor(x, y);
}

void lineto(int x, int y) {
    turtlePen.h = x;
    turtlePen.v = y;
    LineTo(memGraphDC, x, y);
    LineTo(GraphDC, x, y);
	/* Good old Windows does us the favor of leaving out
	   the endpoint, so we have to beat it up */
	LineTo(memGraphDC, x, y+1);
	LineTo(GraphDC, x, y+1);
	MoveCursor(x, y);
}

void win32_go_away(void) {
    int i;

    if (read_line != NULL)
	free(read_line);
    for (i = 0; i < NUM_LINES; i++)
	if (win32_lines[i] != NULL)
	    free(win32_lines[i]);
    if (win32_lines != NULL)
	free(win32_lines);
    DeleteDC(memConDC);
    DeleteDC(memGraphDC);
    DeleteDC(GraphDC);
    DeleteDC(ConDC);
    DeleteObject(turtlePen.hpen);
    DeleteObject(hbrush);
    DeleteObject(textbrush);
    DeleteObject(hbitC);
    DeleteObject(hbitmC);
    DeleteObject(hbitG);
    DeleteObject(hbitmG);
    exit(0);
}

void win32_update_text(void) {
    RECT r;

    GetClientRect(hConWnd, &r);
    BitBlt(ConDC, 0, 0, r.right, r.bottom, memConDC, 0, 0, SRCCOPY);
}

void win32_repaint_screen(void) {
    PostMessage(hMainWnd, WM_SETFOCUS, 0, 0);
}

void win32_advance_line(void) {
    TEXTMETRIC tm;
    RECT r, inv, foo;
    int xpos, ypos;

    GetClientRect(hConWnd, &r);
    GetTextMetrics(memConDC, &tm);

    xpos = (tm.tmAveCharWidth * x_coord);
    ypos = ((tm.tmHeight + tm.tmExternalLeading) * y_coord);

    if ((ypos + 2 * (tm.tmHeight + tm.tmExternalLeading)) >= r.bottom) {
	memmove(myScreen, myScreen+maxXChar, maxXChar*(maxYChar-1));
        ScrollDC(ConDC, 0, - (tm.tmHeight + tm.tmExternalLeading),
		       &r, &r, NULL, &inv);
        FillRect(ConDC, &inv, textbrush);
        foo.left = 0;
        foo.right = Xsofar;
        foo.top = 0;
        foo.bottom = Ysofar;
        ScrollDC(memConDC, 0, - (tm.tmHeight + tm.tmExternalLeading),
		       &foo, &foo, NULL, &inv);
        FillRect(memConDC, &inv, textbrush);
    }
}

void draw_string(char *str) {
    TEXTMETRIC tm;
    int x, y;

    GetTextMetrics(memGraphDC, &tm);
    x = turtlePen.h;
    y = turtlePen.v;
    TextOut(memGraphDC, x, y - tm.tmHeight, str, strlen(str));
    TextOut(GraphDC, x, y - tm.tmHeight, str, strlen(str));
    /* restore position */
    moveto(x, y);
}

char *eight_dot_three_helper(char *in) {
    static char out[20];
    char *filename, *extension;

    filename = strtok(in, ".");
    extension = strtok(NULL, ".");

    if (extension)
        sprintf(out, "%.8s.%.3s", filename, extension);
    else
        sprintf(out, "%.8s", filename);
    return out;
}

char *eight_dot_three(char *in) {
    static char out[100];
    char *last, *last_sep, *fear;
    int index;

    if (last_sep = strrchr(in, '\\')) {
        index = last_sep - in;
        index++;
        strncpy(out, in, index);
        out[index] = '\0';
        last = (char *)malloc((strlen(in) - index + 3) * sizeof(char));
        strncpy(last, &in[index], strlen(in) - index);
        last[strlen(in) - index] = '\0';
    } else if (last_sep = strrchr(in, ':')) {
        index = last_sep - in;
        index++;
        strncpy(out, in, index);
        out[index] = '\0';
        last = (char *)malloc((strlen(in) - index + 3) * sizeof(char));
        strncpy(last, &in[index], strlen(in) - index);
        last[strlen(in) - index] = '\0';
    }
    for (fear = last; *fear != '\0'; fear++)
        if (*fear == '?') *fear = 'Q';
    fear = eight_dot_three_helper(last);
    strcat(out, fear);
    free(last);
    return out;
}

void MoveCursor(int newx, int newy) {
    MoveToEx(memGraphDC, newx, newy, NULL);
    MoveToEx(GraphDC, newx, newy, NULL);
}

void win32_erase_screen(void) {
    PatBlt(memGraphDC, 0, 0, maxX, maxY, PATCOPY);
    PatBlt(GraphDC, 0, 0, maxX, maxY, PATCOPY);
}

void win32_set_bg(FIXNUM c) {
    /*
     * set global variables that denote the background color, create a new
     * brush with the desired color, repaint the memory context, wait for
     * WM_PAINT, and redraw the old display (using the records...)
     */
    back_ground = c;
    hbrush = CreateSolidBrush(palette[back_ground+2]);
    SelectObject(memGraphDC, hbrush);
    DeleteObject(SelectObject(GraphDC, hbrush));
    PatBlt(memGraphDC, 0, 0, maxX, maxY, PATCOPY);
    PatBlt(GraphDC, 0, 0, maxX, maxY, PATCOPY);
    SetBkColor(memGraphDC, palette[back_ground+2]);
    SetBkColor(GraphDC, palette[back_ground+2]);
    redraw_graphics();
}

void win32_clear_text(void) {
    // RECT r;
    /*
     * Draw over the entire client area, so that in split screen mode, for
     * example, there are no ghosts, when the user returns to textscreen mode.
     */

    // GetClientRect(hConWnd, &r);
    memset(myScreen, ' ',maxXChar*maxYChar);
    FillRect(ConDC, &allRect, textbrush);
    FillRect(memConDC, &allRect, textbrush);
}

LRESULT CALLBACK GraphWindowFunc(HWND hwnd, UINT message, WPARAM wParam,
				 LPARAM lParam) {
    HFONT hfont;
    PAINTSTRUCT ps;

    switch (message) {
        case WM_CREATE:
            /*
             * Now, create a copy of the entire screen in memory to act as a virtual
             * window to record turtle motions so they can be repainted in a
             * WM_PAINT message.
             */
            GraphDC = GetDC(hwnd);
            memGraphDC = CreateCompatibleDC(GraphDC);
            maxX = GetSystemMetrics(SM_CXSCREEN);
            maxY = GetSystemMetrics(SM_CYSCREEN);
            hbitmG = CreateCompatibleBitmap(GraphDC, maxX, maxY);
            SelectObject(memGraphDC, hbitmG);
            hbitG = CreateCompatibleBitmap(GraphDC, maxX, maxY);
            SelectObject(GraphDC, hbitG);
	    if (oemfont) {
		hfont = GetStockObject(OEM_FIXED_FONT);
		SelectObject(memGraphDC, hfont);
		SelectObject(GraphDC, hfont);
	    }
	    if (ansifont) {
		hfont = GetStockObject(ANSI_FIXED_FONT);
		SelectObject(memGraphDC, hfont);
		SelectObject(GraphDC, hfont);
	    }
            /*
             * first-time initialization of the brush for the background requires
             * setting the back_ground, and selecting the right color.  while
             * there may be stock objects that contain the right color for the
             * initial pen/brush, subsequent changes might try to delete these
             * "stock" pens/brushes, and that would be "Bad".
             */
            back_ground = 0;
            hbrush = CreateSolidBrush(palette[back_ground+2]);
            SetBkColor(memGraphDC, palette[back_ground+2]);
            SetBkColor(GraphDC, palette[back_ground+2]);
            SelectObject(memGraphDC, hbrush);
            SelectObject(GraphDC, hbrush);

            /*
             * first-time initialization of the pen structure requires setting up
             * all of the fields in the data first, then creating a new pen object
             */
            turtlePen.h = 0; /* ?? */
            turtlePen.v = 0; /* ?? */
            turtlePen.vis = 0;
            turtlePen.mode = WIN_PEN_DOWN;
            turtlePen.width = 1; /* default */
            turtlePen.color = pen_color = old_pen_color = 7;
            /* turtlePen.pattern = ... ? */
            turtlePen.hpen = CreatePen(PS_SOLID, turtlePen.width,
				       palette[2+turtlePen.color]);
            SetTextColor(memGraphDC, palette[2+turtlePen.color]);
            SetTextColor(GraphDC, palette[2+turtlePen.color]);
            SelectObject(memGraphDC, turtlePen.hpen);
            SelectObject(GraphDC, turtlePen.hpen);
            PatBlt(memGraphDC, 0, 0, maxX, maxY, PATCOPY);
            PatBlt(GraphDC, 0, 0, maxX, maxY, PATCOPY);
            break;
	case WM_LBUTTONDOWN:
	    w_button = 1;
	    goto abutton;
	case WM_RBUTTONDOWN:
	    w_button = 2;
	    goto abutton;
	case WM_MBUTTONDOWN:
	    w_button = 3;
abutton:
	    SetCapture(hwnd);
	case WM_MOUSEMOVE:
	    mouse_x = (LOWORD(lParam))-(screen_width/2);
	    mouse_y = (screen_height/2)-(HIWORD(lParam));
	    break;
	case WM_LBUTTONUP:
	case WM_RBUTTONUP:
	case WM_MBUTTONUP:
	    w_button = 0;
	    ReleaseCapture();
	    break;
        case WM_USER:
            InvalidateRect(hGraphWnd, NULL, 1);
        case WM_PAINT:
            BeginPaint(hGraphWnd, &ps);
            BitBlt(ps.hdc, 0, 0, maxX, maxY, memGraphDC, 0, 0, SRCCOPY);
            EndPaint(hGraphWnd, &ps);
            break;
        case WM_DESTROY:  /* end program */
	    PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}

void win32_text_cursor(void) {
    print_char(stdout, '_');
    update_coords('\b');
}

void DrawBoxOutline(POINT ptBeg, POINT ptEnd) {
    SetROP2(ConDC, R2_NOT);
    SelectObject(ConDC, GetStockObject(NULL_BRUSH));
    Rectangle(ConDC, ptBeg.x, ptBeg.y, ptEnd.x, ptEnd.y);
}

LRESULT CALLBACK ConsoleWindowFunc(HWND hwnd, UINT message, WPARAM wParam,
				   LPARAM lParam) {
    HFONT hfont;
    PAINTSTRUCT ps;
    RECT rect;
    TEXTMETRIC tm;
    HGLOBAL hClipMemory, hCopyText;
    PSTR pClipMemory, copyText;
    static int fBlocking=FALSE, haveBlock=FALSE;
    static POINT ptBeg, ptEnd;
    static int chBegX, chBegY, chEndX, chEndY;
    char *copyPtr;
    int i,j;

    switch (message) {
        case WM_CREATE:
            ConDC = GetDC(hwnd);
            memConDC = CreateCompatibleDC(ConDC);
            allRect.left = allRect.top = 0;
            allRect.right = maxX = GetSystemMetrics(SM_CXSCREEN);
            allRect.bottom = maxY = GetSystemMetrics(SM_CYSCREEN);
            hbitmC = CreateCompatibleBitmap(ConDC, maxX, maxY);
            SelectObject(memConDC, hbitmC);
            hbitC = CreateCompatibleBitmap(ConDC, maxX, maxY);
            SelectObject(ConDC, hbitC);
	    if (oemfont) {
		hfont = GetStockObject(OEM_FIXED_FONT);
		SelectObject(memConDC, hfont);
		SelectObject(ConDC, hfont);
	    }
	    if (ansifont) {
		hfont = GetStockObject(ANSI_FIXED_FONT);
		SelectObject(memConDC, hfont);
		SelectObject(ConDC, hfont);
	    }
            textbrush = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            SelectObject(memConDC, textbrush);
            SelectObject(ConDC, textbrush);
            FillRect(memConDC, &allRect, textbrush);
            GetClientRect(hConWnd, &rect);
            GetTextMetrics(ConDC, &tm);
	    maxXChar = maxX / tm.tmAveCharWidth;
	    maxYChar = maxY / (tm.tmHeight + tm.tmExternalLeading);
	    myScreen = (char *)malloc(maxXChar * maxYChar);
	    memset(myScreen, ' ',maxXChar*maxYChar);
            ibm_plain_mode();
            break;
	case WM_LBUTTONDOWN:
	    if (haveBlock) DrawBoxOutline(ptBeg, ptEnd);
	    GetTextMetrics(memConDC, &tm);
	    ptEnd.x = LOWORD(lParam);
	    ptEnd.y = HIWORD(lParam);
	    ptEnd.x = (ptEnd.x/tm.tmAveCharWidth) * tm.tmAveCharWidth;
	    ptEnd.y = (ptEnd.y/(tm.tmHeight + tm.tmExternalLeading))
			* (tm.tmHeight + tm.tmExternalLeading);
	    ptBeg.x = ptEnd.x;
	    ptBeg.y = ptEnd.y;
	    DrawBoxOutline(ptBeg, ptEnd);
	    SetCapture(hwnd);
	    fBlocking = TRUE;
	    haveBlock = FALSE;
	    return 0;
	case WM_MOUSEMOVE:
	    GetTextMetrics(memConDC, &tm);
	    if (fBlocking) {
		DrawBoxOutline(ptBeg, ptEnd);
		ptEnd.x = LOWORD(lParam);
		ptEnd.y = HIWORD(lParam);
		ptEnd.x = (ptEnd.x/tm.tmAveCharWidth) * tm.tmAveCharWidth;
		ptEnd.y = (ptEnd.y/(tm.tmHeight + tm.tmExternalLeading))
			    * (tm.tmHeight + tm.tmExternalLeading);
		DrawBoxOutline(ptBeg, ptEnd);
	    }
	    return 0;
	case WM_LBUTTONUP:
	    if (fBlocking) {
		ReleaseCapture();
		fBlocking = FALSE;
		haveBlock=TRUE;
		GetClientRect(hConWnd, &rect);
		if (ptEnd.x >= 0 && ptEnd.y >= 0 && ptEnd.x < rect.right &&
			ptEnd.y < rect.bottom) {
		    GetTextMetrics(memConDC, &tm);
		    chBegX = ptBeg.x/tm.tmAveCharWidth;
		    chBegY = ptBeg.y/(tm.tmHeight + tm.tmExternalLeading);
		    chEndX = ptEnd.x/tm.tmAveCharWidth;
		    chEndY = ptEnd.y/(tm.tmHeight + tm.tmExternalLeading);
		} else {
		    DrawBoxOutline(ptBeg, ptEnd);
		    haveBlock = FALSE;
		}
	    }
	    return 0;
        case WM_CHAR:
	    if (haveBlock) DrawBoxOutline(ptBeg, ptEnd);
            if (char_mode) {
	        buffered_char = (char)wParam;
	        char_avail++;
		haveBlock = FALSE;
	        return 0;
            }
	    print_space(stdout);	    // flush cursor
	    update_coords('\b');
            if ((char) wParam == '\b') {
	        if (cur_index > 0) {
		    update_coords('\b');
		    print_space(stdout);
		    update_coords('\b');
		    win32_text_cursor();
		    cur_index--;
		} else {
		    MessageBeep(0);
		    haveBlock = FALSE;
		    win32_lines[cur_line][cur_index++] = (char) wParam;
		    print_char(stdout, (char) wParam);
		    win32_text_cursor();
		}
            } else if ((char) wParam == '\r') {    // line ready, let's go!
	        print_char(stdout, '\n');
		haveBlock = FALSE;
	        win32_lines[cur_line][cur_index++] = '\n'; // reader code expects \n
		win32_lines[cur_line][cur_index] = '\0';
	        cur_len = cur_index;
	        read_line = (char *)malloc((strlen(win32_lines[cur_line]) + 1) *
				                sizeof(char));
	        strncpy(read_line, win32_lines[cur_line], cur_index + 1);
	        line_avail = 1;
	        read_index = 0;
	        if (++cur_line >= NUM_LINES)
	            cur_line %= NUM_LINES;  // wrap around
	        cur_index = 0;
	        return 0;
            } else if ((char)wParam == 17 || (char)wParam == 23) {
		haveBlock = FALSE;
	        print_char(stdout, '\n');
	        read_line = (char *)malloc(sizeof(char));
	        *read_line = (char)wParam;
	        line_avail = 1;
	        read_index = 0;
	        cur_index = 0;
	        return 0;
	    } else if ((char)wParam == 3 && haveBlock) { /* ^C for copy */
		int temp;
		haveBlock = FALSE;
		if (chEndX < chBegX) {
		    temp = chEndX;
		    chEndX = chBegX;
		    chBegX = temp;
		}
		if (chEndY < chBegY) {
		    temp = chEndY;
		    chEndY = chBegY;
		    chBegY = temp;
		}
		hCopyText = GlobalAlloc(GHND,
					(chEndY-chBegY)*(2+chEndX-chBegX)+1);
		copyText = GlobalLock(hCopyText);
		copyPtr = copyText;
		for (i=chBegY; i<chEndY; i++) {
		    for (j=chBegX; j<chEndX; j++)
			*copyPtr++ = myScreen[i*maxXChar + j];
		    *copyPtr++ = '\r';
		    *copyPtr++ = '\n';
		}
		*(copyPtr-2) = '\0';	/* no newline at end */
		GlobalUnlock(hCopyText);
		if (OpenClipboard(hwnd)) {
		    EmptyClipboard();
		    SetClipboardData(CF_TEXT,hCopyText);
		    CloseClipboard();
		}
		return 0;
	    } else if ((char)wParam == 22) {	/* ^V for paste */
		haveBlock = FALSE;
		if (OpenClipboard(hwnd)) {
		    hClipMemory = GetClipboardData(CF_TEXT);
		    if (hClipMemory != NULL) {
			winPasteText = (char *)malloc(GlobalSize(hClipMemory));
			pClipMemory = GlobalLock(hClipMemory);
			strcpy(winPasteText, pClipMemory);
			winPastePtr = winPasteText;
		    }
		    GlobalUnlock(hClipMemory);
		    CloseClipboard();
		    winDoPaste();
		}
            } else {
		haveBlock = FALSE;
	        win32_lines[cur_line][cur_index++] = (char) wParam;
	        print_char(stdout, (char) wParam);
		win32_text_cursor();
            }
            break;
       case WM_USER:
            InvalidateRect(hConWnd, NULL, 1);
       case WM_PAINT:
	    haveBlock = FALSE;
            BeginPaint(hConWnd, &ps);
            GetClientRect(hConWnd /* ps.hdc */ , &rect);
            BitBlt(ps.hdc, 0, 0, rect.right, rect.bottom, memConDC, 0, 0,
		   SRCCOPY);
            EndPaint(hConWnd, &ps);
            break;
        case WM_DESTROY:  /* end program */
	    PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst, LPSTR lpszArgs,
		   int nWinMode) {
    NODE *exec_list = NIL;
    WNDCLASSEX wGraphCL, wConCL, wParentCL;
    RECT r;
    TEXTMETRIC tm;
    int i;
    char *argv[20], *cp;
    int argc;
    char *fontenv;

    fontenv = getenv("LOGOFONT");
    if (fontenv != NULL && !strcmp(fontenv,"oem")) {
	oemfont = TRUE;
	ansifont = FALSE;
    }
    if (fontenv != NULL && !strcmp(fontenv,"ansi")) {
	ansifont = TRUE;
	oemfont = FALSE;
    }
    if (fontenv != NULL && !strcmp(fontenv,"default")) {
	ansifont = FALSE;
	oemfont = FALSE;
    }

    /* Set up the parameters that define the Graphics window's class */
    wParentCL.hInstance = hThisInst;
    wParentCL.lpszClassName = szWinName;
    wParentCL.lpfnWndProc = ParentWindowFunc;
    wParentCL.style = 0;
    wParentCL.cbSize = sizeof(WNDCLASSEX);
    wParentCL.hIcon = LoadIcon(hThisInst, (char *)IDI_ICON1);
    wParentCL.hIconSm = LoadIcon(hThisInst, (char *)IDI_ICON2);
    wParentCL.hCursor = LoadCursor(NULL, IDC_ARROW);
    wParentCL.lpszMenuName = NULL;
    wParentCL.cbClsExtra = 0;
    wParentCL.cbWndExtra = 0;
    wParentCL.hbrBackground = (void *)(COLOR_WINDOWFRAME+1);

    /* Set up the parameters that define the "Console" window's class */
    wConCL.hInstance = hThisInst;
    wConCL.lpszClassName = szConWinName;
    wConCL.lpfnWndProc = ConsoleWindowFunc;
    wConCL.style = 0;
    wConCL.cbSize = sizeof(WNDCLASSEX);
    wConCL.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wConCL.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wConCL.hCursor = LoadCursor(NULL, IDC_ARROW);
    wConCL.lpszMenuName = NULL;
    wConCL.cbClsExtra = 0;
    wConCL.cbWndExtra = 0;
    wConCL.hbrBackground = (void *)(COLOR_WINDOW+1);

    /* Set up the parameters that define the Graphics window's class */
    wGraphCL.hInstance = hThisInst;
    wGraphCL.lpszClassName = szGraphWinName;
    wGraphCL.lpfnWndProc = GraphWindowFunc;
    wGraphCL.style = 0;
    wGraphCL.cbSize = sizeof(WNDCLASSEX);
    wGraphCL.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wGraphCL.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wGraphCL.hCursor = LoadCursor(NULL, IDC_ARROW);
    wGraphCL.lpszMenuName = NULL;
    wGraphCL.cbClsExtra = 0;
    wGraphCL.cbWndExtra = 0;
    wGraphCL.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);

    /* Initially, the program is in its "Text only" mode */
    in_graphics_mode = in_splitscreen = 0;

    /* Register the Parent (main) window */
    if (!RegisterClassEx(&wParentCL))
        return 0;
    /* Register the Graphics (child) window */
    if (!RegisterClassEx(&wGraphCL))
        return 0;
    /* Register the Console (child) window */
    if (!RegisterClassEx(&wConCL))
        return 0;

    win32_init_palette();

    hMainWnd = CreateWindow(szWinName, "UCB Logo", WS_OVERLAPPEDWINDOW,
			    CW_USEDEFAULT, CW_USEDEFAULT,
			    700, 440,
			    HWND_DESKTOP,
			    NULL, hThisInst, NULL);

    win32_lines = (char **) malloc(NUM_LINES * sizeof(char *));
    if (win32_lines == NULL) {
        // print some message and puke
    }
    for (i = 0; i < NUM_LINES; i++)
        win32_lines[i] = (char *) malloc(CHARS_PER_LINE * sizeof(char));
    so_arr[0] = '\1'; so_arr[1] = '\0';
    se_arr[0] = '\2'; se_arr[1] = '\0';

    ShowWindow(hMainWnd, nWinMode);
    UpdateWindow(hMainWnd);

    /*
     * at this point, all of the windows have been created, so it's safe to
     * check on their size
     */

    GetTextMetrics(memConDC, &tm);
    GetClientRect(hConWnd, &r);

    x_coord = y_coord = 0;

    x_max = r.right;
    x_max /= tm.tmAveCharWidth;
    x_max--;  // this eliminates splitting a character over the right border

    y_max = r.bottom;
    y_max /= (tm.tmHeight + tm.tmExternalLeading);
    y_max--;
    Xsofar = r.right;
    Ysofar = r.bottom;

    argv[0] = "ucblogo";
    argc = 1;
    if (lpszArgs != NULL) {
	cp = lpszArgs;
	while (*cp != '\0' && *cp != '\n' && *cp != '\r') {
	    if (*cp == '"') {
		argv[argc++] = ++cp;
		while (*cp != '"' && *cp != '\0') cp++;
		if (*cp == '"') *cp++ = '\0';
	    } else {
		argv[argc++] = cp;
		while (*cp != '\0' && *cp != ' ' && *cp != '\t'
		       && *cp != '\n' && *cp != '\r') cp++;
		if (*cp != '\0') *cp++ = '\0';
	    }
	    while (*cp == ' ' || *cp == '\t') cp++;
	}
    }

/*
    if (lpszArgs != NULL && (argv[1] = strtok(lpszArgs, " \t\r\n")) != NULL) {
	argc = 2;
	while (argv[argc] = strtok(NULL, " \t\r\n"))
	    argc++;
    }
 */

    (void) main(argc, argv);
    win32_go_away();
    return 0;
}

void winDoPaste(void) {
    char ch = *winPastePtr++;
    while (ch != '\0') {
	if (ch == '\r') {
	    print_char(stdout, '\n');
	    win32_lines[cur_line][cur_index++] = '\n';
	    cur_len = cur_index;
	    read_line = (char *)malloc((strlen(win32_lines[cur_line]) + 1) *
					    sizeof(char));
	    strncpy(read_line, win32_lines[cur_line], cur_index + 1);
	    line_avail = 1;
	    read_index = 0;
	    if (++cur_line >= NUM_LINES)
		cur_line %= NUM_LINES;  // wrap around
	    cur_index = 0;
	    return;
	}
	if (ch != 3 && ch != 22 && ch != '\n') {
	    win32_lines[cur_line][cur_index++] = ch;
	    print_char(stdout, ch);
	}
	ch = *winPastePtr++;
    }
    free(winPasteText);
    winPasteText = NULL;
}

NODE *win32_get_node_pen_pattern(void) {
    return cons(make_intnode(-1), NIL);
}

NODE *maximize(NODE *args) {
	int big=torf_arg(args);
	ShowWindow(hMainWnd, (big ? SW_MAXIMIZE : SW_RESTORE));
    UpdateWindow(hMainWnd);
	return UNBOUND;
}

void logofill(void) {
    COLORREF col;

    hbrush = CreateSolidBrush(palette[2+pen_color]);
    SelectObject(memGraphDC, hbrush);
    DeleteObject(SelectObject(GraphDC, hbrush));
    col = GetPixel(memGraphDC, g_round(screen_x_coord), g_round(screen_y_coord));
    (void)ExtFloodFill(memGraphDC, g_round(screen_x_coord),
		         g_round(screen_y_coord), col, FLOODFILLSURFACE);
    (void)ExtFloodFill(GraphDC, g_round(screen_x_coord),
		         g_round(screen_y_coord), col, FLOODFILLSURFACE);
    hbrush = CreateSolidBrush(palette[2+back_ground]);
    SelectObject(memGraphDC, hbrush);
    DeleteObject(SelectObject(GraphDC, hbrush));
}

void get_pen_pattern(void) {
}

void set_pen_pattern(void) {
}

void get_palette(int slot, unsigned int *r, unsigned int *g, unsigned int *b) {
    if (slot > 263 || (slot >= 0 && slot < 8) || slot < -2)  // 256 rgb values
        return;
    slot+=2;
    *b = ((palette[slot % 264] & 0x00ff0000) >> 16) * 256;
    *g = ((palette[slot % 264] & 0x0000ff00) >> 8) * 256;
    *r = (palette[slot % 264] & 0x000000ff) * 256;
}

void set_palette(int slot, unsigned int r, unsigned int g, unsigned int b) {
    if (slot > 263 || (slot >= 0 && slot < 8) || slot < -2)  // 256 rgb values
        return;
    slot+=2;
    palette[slot] = RGB(r/256, g/256, b/256);
}

void save_pen(pen_info *p) {
    POINT pt;

    p->vis = pen_vis;
    p->color = pen_color;
    GetCurrentPositionEx(memGraphDC, &pt);
    p->h = (int) pt.x;
    p->v = (int) pt.y;
    p->width = turtlePen.width;
    p->mode = turtlePen.mode;
}

void restore_pen(pen_info *p) {
    pen_vis = p->vis;
    MoveCursor(p->h, p->v);
    turtlePen.mode = p->mode;
    win32_set_pen_mode(turtlePen.mode);
    turtlePen.width = p->width;
    pen_color = p->color;
    turtlePen.hpen = CreatePen(PS_SOLID, turtlePen.width,
					 (in_erase_mode ? palette[2+back_ground]
							: palette[2+pen_color]));
    SelectObject(memGraphDC, turtlePen.hpen);
    DeleteObject(SelectObject(GraphDC, turtlePen.hpen));
}

void set_list_pen_pattern(void) {
}

void label(char *str) {
    draw_string(str);
}

void plain_xor_pen(void) {
    win32_set_pen_mode(WIN_PEN_REVERSE);
}

BOOLEAN check_ibm_stop(void) {
    MSG msg;
    SHORT s;
    static int count;

    if ((s = GetAsyncKeyState(VK_CONTROL)) < 0) {
        if ((s = GetAsyncKeyState(65 + ('q' - 'a')) < 0)) {
            err_logo(STOP_ERROR,NIL);
            return (1);
        }
        if ((s = GetAsyncKeyState(65 + ('w' - 'a')) < 0)) {
            // control w
            to_pending = 0;
            lpause(NIL);
        }
    }
    count++;
    count %= 300;  // empirical value

    if (!count) {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT)
		;
            else {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
            }
        }
    }
    return FALSE;
}

void win32_con_full_screen(void) {
    RECT r;
    PAINTSTRUCT ps;

    if (in_graphics_mode && !in_splitscreen)
        return;
    in_graphics_mode = 1;
    in_splitscreen = 0;

    GetClientRect(hMainWnd, &r);
    /* position coordinates are relative to the parent's frame. */
    MoveWindow(hGraphWnd, 0, 0, r.right, r.bottom, TRUE);
    ShowWindow(hConWnd, SW_HIDE);
    ShowWindow(hGraphWnd, SW_SHOWNORMAL);
    BeginPaint(hGraphWnd, &ps);
    BitBlt(ps.hdc, 0, 0, maxX, maxY, memGraphDC, 0, 0, SRCCOPY);
    EndPaint(hGraphWnd, &ps);
    redraw_graphics();
}

void win32_prepare_draw(void) {
    if (in_graphics_mode) return;
    win32_con_split_screen();
}

long upscroll_text(int limit) {
    TEXTMETRIC tm;
    RECT r, inv;
    int y_new, offset, old, rbot;

    GetClientRect(hMainWnd, &r);
    GetTextMetrics(memConDC, &tm);
    y_new = r.bottom;
    y_new /= (tm.tmHeight + tm.tmExternalLeading);
    rbot = y_new * (tm.tmHeight + tm.tmExternalLeading);
    y_new--;
    if (limit > 0 && limit < y_new) {
        y_new = limit;
        rbot = r.bottom = (limit+1) * (tm.tmHeight + tm.tmExternalLeading);
    }
    offset = y_coord - y_new;
    if (offset > 0) {
        old = (y_coord + 1) * (tm.tmHeight + tm.tmExternalLeading);
        BitBlt(ConDC, 0, 0, maxX, maxY, memConDC, 0, old-rbot, SRCCOPY);
        inv.left = 0;
        inv.right = allRect.right;
        inv.top = rbot - 1;
        inv.bottom = allRect.bottom;
        BitBlt(memConDC, 0, 0, maxX, maxY, ConDC, 0, 0, SRCCOPY);
        FillRect(memConDC, &inv, textbrush);
        y_coord = y_new;
    }
    return r.bottom;
}

void reshow_text(void) {
    TEXTMETRIC tm;
    RECT r, foo;

    ShowWindow(hConWnd, SW_SHOW);
    GetClientRect(hConWnd, &r);
    GetTextMetrics(memConDC, &tm);
    FillRect(ConDC, &r, textbrush);
    if (r.right > Xsofar) {
        foo.left = Xsofar;
        foo.right = r.right;
        foo.top = 0;
        foo.bottom = allRect.bottom;
        FillRect(memConDC, &foo, textbrush);
        Xsofar = r.right;
    }
    if (r.bottom > Ysofar) {
        foo.left = 0;
        foo.right = r.right;
        foo.top = Ysofar;
        foo.bottom = r.bottom;
        FillRect(memConDC, &foo, textbrush);
        Ysofar = r.bottom;
    }
    BitBlt(ConDC, 0, 0, r.right, r.bottom, memConDC, 0, 0, SRCCOPY);
    ValidateRect(hConWnd, &r);

    x_max = r.right;
    x_max /= tm.tmAveCharWidth;
    x_max--;

    y_max = r.bottom;
    y_max /= (tm.tmHeight + tm.tmExternalLeading);
    y_max--;
}

void win32_con_split_screen(void) {
    RECT r;
    PAINTSTRUCT ps;
    long vert;

    if (in_graphics_mode && in_splitscreen)
        return;
    in_graphics_mode = in_splitscreen = 1;

    ShowWindow(hConWnd, SW_SHOW);
    vert = upscroll_text(3);
    GetClientRect(hMainWnd, &r);
    MoveWindow(hConWnd, 0, r.bottom - vert - 6, r.right, vert + 6, FALSE);
    MoveWindow(hGraphWnd, 0, 0, r.right, r.bottom - vert - 8, TRUE);
    ShowWindow(hGraphWnd, SW_HIDE);
    BeginPaint(hGraphWnd, &ps);
    BitBlt(ps.hdc, 0, 0, maxX, maxY, memGraphDC, 0, 0, SRCCOPY);
    EndPaint(hGraphWnd, &ps);
    reshow_text();
    redraw_graphics();
    ShowWindow(hGraphWnd, SW_SHOW);
    if (!seen_once) {
        seen_once = TRUE;
        lclearscreen(NIL);
    }
}

void win32_con_text_screen(void) {
    RECT r;

    if (!in_graphics_mode)
        return;
    in_graphics_mode = in_splitscreen = 0;

    (void)upscroll_text(0);
    GetClientRect(hMainWnd, &r);
    MoveWindow(hConWnd, 0, 0, r.right, r.bottom, FALSE);
    ShowWindow(hGraphWnd, SW_HIDE);
    reshow_text();
}
    
void win32_turtle_prep(void) {
    if (in_erase_mode) {
        /* current pen color != "real" pen color */
        turtlePen.hpen = CreatePen(PS_SOLID, turtlePen.width, palette[2+pen_color]);
	SelectObject(memGraphDC, turtlePen.hpen);
	DeleteObject(SelectObject(GraphDC, turtlePen.hpen));
    } else {
	SelectObject(memGraphDC, turtlePen.hpen);
	SelectObject(GraphDC, turtlePen.hpen);
    }
    update_pos = FALSE;
    pre_turtle_pen_mode = SetROP2(memGraphDC, R2_XORPEN);
    (void)SetROP2(GraphDC, R2_XORPEN);
}

void win32_turtle_end(void) {
    if (in_erase_mode) {
        /*
         * current pen color should now be set to background color to resume
         * "erase mode"
         */
        turtlePen.hpen = CreatePen(PS_SOLID, turtlePen.width, palette[2+back_ground]);
	SelectObject(memGraphDC, turtlePen.hpen);
	DeleteObject(SelectObject(GraphDC, turtlePen.hpen));
    } else {
	SelectObject(memGraphDC, turtlePen.hpen);
	SelectObject(GraphDC, turtlePen.hpen);
    }
    update_pos = TRUE;
    SetROP2(memGraphDC, pre_turtle_pen_mode);
    SetROP2(GraphDC, pre_turtle_pen_mode);
}

void win32_init_palette(void) {
    palette[2] = RGB(0, 0, 0); /* black */
    palette[3] = RGB(0, 0, 255); /* blue */
    palette[4] = RGB(0, 255, 0); /* green */
    palette[5] = RGB(0, 255, 255); /* cyan */
    palette[6] = RGB(255, 0, 0); /* red */
    palette[7] = RGB(255, 0, 255); /* magenta */
    palette[8] = RGB(255, 255, 0); /* yellow */
    palette[9] = RGB(255, 255, 255); /* white */
    palette[10] = RGB(155, 96, 59);    /* brown */
    palette[11] = RGB(197, 136, 18); /* tan */
    palette[12] = RGB(100, 162, 64); /* forest */
    palette[13] = RGB(120, 187, 187); /* aqua */
    palette[14] = RGB(255, 149, 119); /* salmon */
    palette[15] = RGB(144, 113, 208); /* purple */
    palette[16] = RGB(255, 163, 0); /* orange */
    palette[17] = RGB(183, 183, 183); /* gray */
    /* rest are user defined */
}

void win32_set_pen_color(int c) {
    draw_turtle();
    turtlePen.color = pen_color = c;
    if (!in_erase_mode) {
	turtlePen.hpen = CreatePen(PS_SOLID, turtlePen.width,
				   palette[2+turtlePen.color]);
	SelectObject(memGraphDC, turtlePen.hpen);
	DeleteObject(SelectObject(GraphDC, turtlePen.hpen));
    }
    SetTextColor(memGraphDC, palette[2+pen_color]);
    SetTextColor(GraphDC, palette[2+pen_color]);
    draw_turtle();
}

int win32_set_pen_width(int w) {
    int old;

    old = turtlePen.width;
    turtlePen.width = w;
    turtlePen.hpen = CreatePen(PS_SOLID, turtlePen.width,
			         (in_erase_mode ? palette[2+back_ground]
						: palette[2+turtlePen.color]));
    SelectObject(memGraphDC, turtlePen.hpen);
    DeleteObject(SelectObject(GraphDC, turtlePen.hpen));
    return old;
}

NODE *win32_lsetcursor(NODE *args) {
    NODE *arg;
    TEXTMETRIC tm;
    int xpos, ypos;

    GetTextMetrics(memConDC, &tm);

    arg = pos_int_vector_arg(args);
    if (NOT_THROWING) {
        x_coord = x_margin + getint(car(arg));
        y_coord = y_margin + getint(cadr(arg));
        while ((x_coord >= x_max || y_coord >= y_max) && NOT_THROWING) {
            setcar(args, err_logo(BAD_DATA, arg));
            if (NOT_THROWING) {
		arg = pos_int_vector_arg(args);
		x_coord = x_margin + getint(car(arg));
		y_coord = y_margin + getint(cadr(arg));
	    }
	}
    }

    xpos = tm.tmAveCharWidth * x_coord;
    ypos = (tm.tmHeight + tm.tmExternalLeading) * y_coord;

    if (NOT_THROWING) {
        MoveToEx(memConDC, xpos, ypos, NULL);
        MoveToEx(ConDC, xpos, ypos, NULL);
    }
    return(UNBOUND);
}

void win32_pen_erase(void) {
    win32_set_pen_mode(WIN_PEN_ERASE);
}

void win32_pen_down(void) {
    win32_set_pen_mode(WIN_PEN_DOWN);
}

void win32_pen_reverse(void) {
    win32_set_pen_mode(WIN_PEN_REVERSE);
}

void win32_set_pen_mode(int newmode) {
    int rop2_mode, newpc;

    turtlePen.mode = newmode;

    if (newmode == WIN_PEN_ERASE)
        in_erase_mode = TRUE;
    else
        in_erase_mode = FALSE;

    if (newmode == WIN_PEN_REVERSE)
        rop2_mode = R2_XORPEN;
    else
        rop2_mode = R2_COPYPEN;

    SetROP2(memGraphDC, rop2_mode);
    SetROP2(GraphDC, rop2_mode);

    if (newmode == WIN_PEN_ERASE)
	newpc = back_ground;
    else
	newpc = pen_color;
    turtlePen.hpen = CreatePen(PS_SOLID, turtlePen.width, palette[2+newpc]);
    SelectObject(memGraphDC, turtlePen.hpen);
    DeleteObject(SelectObject(GraphDC, turtlePen.hpen));
}

LRESULT CALLBACK ParentWindowFunc(HWND hwnd, UINT message, WPARAM wParam,
				 LPARAM lParam) {
    RECT r;

    switch (message) {
        case WM_CHAR:
		SendMessage(hConWnd, WM_CHAR, wParam, lParam);
            break;
        case WM_SIZE:
	case WM_EXITSIZEMOVE:
	    if (wParam != SIZE_MINIMIZED) {
		if (!in_graphics_mode) {   // Text screen mode
		    in_graphics_mode = 1;
		    win32_con_text_screen();
		} else if (in_splitscreen) {
		    in_splitscreen = 0;
		    win32_con_split_screen();
		} else {
		    in_graphics_mode = 0;
		    win32_con_full_screen();
		}
	    }
        case WM_MOVE:
        case WM_SETFOCUS:
        case WM_EXITMENULOOP:
            if (!in_graphics_mode || in_splitscreen)
                SendMessage(hConWnd, WM_USER, wParam, lParam);
            if (in_graphics_mode)
                SendMessage(hGraphWnd, WM_USER, wParam, lParam);
	    win32_update_text();
            break;
        case WM_PAINT:
            /*
             * The parent window really has nothing to draw.  Therefore, all that
             * the parent window's paint function does is dispatch the appropriate
             * message to the child window.
             */
            if (!in_graphics_mode || in_splitscreen)
		SendMessage(hConWnd, WM_PAINT, wParam, lParam);
            if (in_graphics_mode)
		SendMessage(hGraphWnd, WM_PAINT, wParam, lParam);
	    GetClientRect(hwnd, &r);
	    ValidateRect(hwnd, &r);
            break;
        case WM_CREATE:
            hConWnd = CreateWindow(szConWinName, NULL,
				   WS_CHILDWINDOW | WS_VISIBLE | WS_BORDER,
				   0, 0, 0, 0,
				   hwnd, (HMENU) (2 << 8),
				   (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
				   NULL);
            hGraphWnd = CreateWindow(szGraphWinName, NULL,
				     WS_CHILDWINDOW | WS_VISIBLE | WS_BORDER,
				     0, 0, 0, 0,
				     hwnd, (HMENU) (4 << 8),
				     (HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),
				     NULL);

            break;
        case WM_DESTROY:  /* end program */
	    win32_go_away();
            break;
        default:
            return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}

void CharOut(int c) {
    TEXTMETRIC tm;
    RECT r;
    int xpos, ypos;
    char nog[3];

    sprintf(nog, "%c", c);

    if (x_coord < maxXChar && y_coord < maxYChar)
	myScreen[(y_coord * maxXChar) + x_coord] = c;

    GetTextMetrics(memConDC, &tm);
    xpos = tm.tmAveCharWidth * x_coord;
    ypos = (tm.tmHeight + tm.tmExternalLeading) * y_coord;

    r.left = xpos;
    r.right = (xpos + tm.tmAveCharWidth);

    r.top = ypos;
    r.bottom = (ypos + tm.tmHeight + tm.tmExternalLeading);

    TextOut(memConDC, xpos, ypos, nog, 1);
    TextOut(ConDC, xpos, ypos, nog, 1);
}

int win32_putc(int c, FILE *strm) {
    if (strm == stdout || strm == stderr) {
        if (c == '\n')
            win32_advance_line();
	else if (c == '\t') /* do nothing */ ;
	else if (c == '\007') tone(400,30);
        else {
            if (x_coord == x_max)
	    new_line(strm);
            CharOut(c);
        }
        return 0;
    }
    return putc(c, strm);
}

void win32_charmode_on(void) {
    char_mode = 1;
}

void win32_charmode_off(void) {
    char_mode = 0;
}

void ibm_bold_mode(void) {
    SetTextColor(memConDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(ConDC, GetSysColor(COLOR_WINDOW));
    SetBkColor(memConDC, GetSysColor(COLOR_WINDOWTEXT));
    SetBkColor(ConDC, GetSysColor(COLOR_WINDOWTEXT));
}

void ibm_plain_mode(void) {
    SetTextColor(memConDC, GetSysColor(COLOR_WINDOWTEXT));
    SetTextColor(ConDC, GetSysColor(COLOR_WINDOWTEXT));
    SetBkColor(memConDC, GetSysColor(COLOR_WINDOW));
    SetBkColor(ConDC, GetSysColor(COLOR_WINDOW));
}

NODE *set_text_color(NODE *args) {
    int fore, back;

    fore = getint(pos_int_arg(args));
    if (NOT_THROWING) {
        back = getint(pos_int_arg(cdr(args)));
        if (NOT_THROWING) {
           SetTextColor(memConDC, palette[2+fore]);
           SetTextColor(ConDC, palette[2+fore]);
           SetBkColor(memConDC, palette[2+back]);
           SetBkColor(ConDC, palette[2+back]);
        }
    }
    return UNBOUND;
}

/* Thanks to George Mills for the tone code! */
/*   was:    MessageBeep(0xffffffff);	*/

void tone(FIXNUM pitch, FIXNUM duration) {
    OSVERSIONINFO VersionInformation;
    unsigned char count_lo;
    unsigned char count_hi;
    FIXNUM count;
    clock_t NumTicksToWait;

    memset(&VersionInformation, 0, sizeof(OSVERSIONINFO));
    VersionInformation.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    if (pitch < 37) pitch = 37;
    GetVersionEx(&VersionInformation);
    if (VersionInformation.dwPlatformId == VER_PLATFORM_WIN32_NT)
	Beep(pitch, duration);
    else {
	count = 1193180L / pitch;
	count_lo = LOBYTE(count);
	count_hi = HIBYTE(count);

	_asm {
	    mov al, 0xB6
	    out 0x43, al
	    mov al, count_lo
	    out 0x42, al
	    mov al, count_hi
	    out 0x42, al
	    xor al, al
	    in al, 0x61
	    or al, 0x03
	    out 0x61, al
	}

	NumTicksToWait = duration + clock();
	while (NumTicksToWait > clock());

	_asm {
	    xor al, al
	    in al, 0x61
	    xor al, 0x03
	    out 0x61, al
	}
    }
}
