/*
Copyright (C) 1998, 1999, 2000 Wabasoft

Modifications for DOS by Mohan Embar
http://www.thisiscool.com/

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later version. 

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details. 

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA. 
*/

#ifdef COMMENT
/*

If you're looking here, you've probably already looked at (or worked on) the
nm<platform>_b.c file. If you've gotten the _a and _b files working, this is
the easy part. This file contains all the native functions that are part of
the waba core framework. When you've added all the native functions then you're
done porting the VM.

The native functions that need to be ported are all those listed in the waba.c
native function table. There are stubs included in this file to make things
a bit easier than starting from scratch.

Some rules:

As a rule for native functions, don't hold a pointer across any call
that could garbage collect. For example, this is bad:

ptr = WOBJ_arrayStart(array)
...
string = createString(..)
ptr[0]

since the createString() could GC, the ptr inside of array could be invalid
after the call since a GC would move memory around. Instead, use:

ptr = WOBJ_arrayStart(array)
...
string = createString(..)
...
ptr = WOBJ_arrayStart(array)
ptr[0]

to recompute the pointer after the possible GC. The main thing to know there is
that when you call VM functions, it might garbage collect, so you want to 
recompute any pointers again after that function returns since the memory
locations might have changed. When you don't call VM functions that garbage
collect, there isn't a problem, because the VM is all single threaded. Things
don't move around on you unless you call a function that can garbage collect.

Another thing to note is that if you subclass a class with an object
destroy function, you must explicity call your superclasses objects
destroy functions. This isn't done automatically. See one of the objects that does
that in nmwin32_c.c for reference.

Before jumping into writing native functions, we need to look at 'classHooks'.
A classHooks array contains 'class hooks' like this:

ClassHook classHooks[] =
	{
	{ "waba/fx/Graphics", NULL, 11},
	{ "waba/fx/Image", ImageDestroy, 1 },
	{ "waba/io/Catalog", CatalogDestroy, 7 },
	{ "waba/io/Socket", SocketDestroy, 2 }, 
	{ "waba/io/SerialPort", SerialPortDestroy, 2 }, 
	{ NULL, NULL }
	};

You will need to define a classHooks array. Its an array of triples:

- a class name
- a object destroy function
- a number of native variables

You need a class hook when an object needs to allocate some native system
resource which it needs to use later on and/or needs to be freed.

For example, a Socket object will probably need to keep around a reference
to a socket descriptor around. And when a socket object is garbage
collected, it should close the socket.

So, it needs a classHook. In the above, the destroy method for a socket
is SocketDestroy and it will get called when the object is garbage
collected. The classHook for the Socket class above has 2 native
variables associated with it. Each of these native variables
are 32 bit values. One could hold a socket descriptor and the
other could hold something else. You can see nmpalm_c.c or nmwin32_c.c
for more on how that works for sockets.

So, how do you access a class hook variable or a variable that is in
a waba object? You can access a waba object's variables directly like this:

#define WOBJ_RectX(o) (objectPtr(o))[1].intValue

The objectPtr(o) gets a pointer to the data in an object and the [1]
references the second value in the object. The first value is a pointer
to the class of the object so the WOBJ_RectX(o) above is a macro that
pulls out the first actual variable in the object which is its X value.

If you look in the Rect class, you'll see that it looks like:

class Rect
{
int x;
int y;
..

So, the first variable - (objectPtr(o))[1].intValue is the x value.

The hook variables get stuck in after all the normal variables. If you look in the
nmwin32_c.c or nmpalm_c.c code, you'll see things like this:

//
// Graphics
//
// var[0] = Class
// var[1] = Surface
// var[2] = hook var - 1 for window surface and 2 for image
// var[3] = hook var - rgb
// var[4] = hook var - has clip
// var[5] = hook var - clipX
// ...

and then:

#define WOBJ_GraphicsSurface(o) (objectPtr(o))[1].obj
#define WOBJ_GraphicsSurfType(o) (objectPtr(o))[2].intValue
#define WOBJ_GraphicsRGB(o) (objectPtr(o))[3].intValue
#define WOBJ_GraphicsHasClip(o) (objectPtr(o))[4].intValue
#define WOBJ_GraphicsClipX(o) (objectPtr(o))[5].intValue
#define WOBJ_GraphicsClipY(o) (objectPtr(o))[6].intValue
#define WOBJ_GraphicsClipWidth(o) (objectPtr(o))[7].intValue

See how the hook variables start right after the last variable in the
class. The above are macros that access the value of a waba object in C code.
Making macros like the above makes the code easier to read.

With the macros, we can inspect a graphic's object clipX with:

int32 x = WObjectGraphicClipX(object);

Of course, if you change the Graphics object and add a new variable before the
surface variable or if you add a new variable in a base class, you need to
recompute those mappings or everything will get messed up.

The best way to port the native functions is to start out by making something
that doesn't work but does compile and then take code from the other native
VM's implementations and hack it up one class at a time. It's probably best
to start with the window and drawing classes so you can see something when
you start out.

*/
#endif // COMMENT

#include "palmemul.c"
#include <pc.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>

// WHEN PORTING: You'll probably need classHooks for these things when you get
// things going. Here we've allocate no hook variables for each object but have
// assigned some object destructor functions so when you actually hook some
// data in there, you'll need to set the 0 values to something else

static Var Return0Func(Var stack[])
	{
	Var v;

	v.obj = 0;
	return v;
	}

static Var ReturnNeg1Func(Var stack[])
	{
	Var v;

	v.intValue = -1;
	return v;
	}

static void GraphicsDestroy(WObject obj);
static void ImageDestroy(WObject obj);
static void CatalogDestroy(WObject obj);
static void FileDestroy(WObject obj);
static void SocketDestroy(WObject obj);
static void SerialPortDestroy(WObject obj);

ClassHook classHooks[] =
	{
	{ "waba/fx/Graphics", NULL, 11},
	{ "waba/fx/Image", ImageDestroy, 2 },
	{ "waba/io/Catalog", CatalogDestroy, 7 },
	{ "waba/io/File", FileDestroy, 1 },
	{ "waba/io/Socket", SocketDestroy, 2 }, 
	{ "waba/io/SerialPort", SerialPortDestroy, 2 }, 
	{ NULL, NULL }
	};

//
// Rect
//
// var[0] = Class
// var[1] = int x
// var[2] = int y
// var[3] = int width
// var[4] = int height

#define WOBJ_RectX(o) (objectPtr(o))[1].intValue
#define WOBJ_RectY(o) (objectPtr(o))[2].intValue
#define WOBJ_RectWidth(o) (objectPtr(o))[3].intValue
#define WOBJ_RectHeight(o) (objectPtr(o))[4].intValue

//
// Control
//
// var[0] = Class
// var[1] = int x
// var[2] = int y
// var[3] = int width
// var[4] = int height

#define WOBJ_ControlX(o) (objectPtr(o))[1].intValue
#define WOBJ_ControlY(o) (objectPtr(o))[2].intValue
#define WOBJ_ControlWidth(o) (objectPtr(o))[3].intValue
#define WOBJ_ControlHeight(o) (objectPtr(o))[4].intValue

//
// Window
//

static Var WindowCreate(Var stack[])
	{
	WObject win;
	Var v;

	win = stack[0].obj;
	WOBJ_ControlX(win) = 0;
	WOBJ_ControlY(win) = 0;
	WOBJ_ControlWidth(win) = g_mainWinWidth;
	WOBJ_ControlHeight(win) = g_mainWinHeight;
	v.obj = 0;
	return v;
	}

//
// MainWindow
//

static Var MainWinCreate(Var stack[])
{
	Var v;
    char* psz;

    if (psz = initPalmEmul())
    {
#ifdef DEBUGVERBOSE
        fprintf(stderr, "%s\n", psz);
        fflush(pfilLog);
#endif
    }
    else
	    globalMainWin = stack[0].obj;
	v.obj = 0;
	return v;
}

static Var MainWinExit(Var stack[])
{
	Var v;

    donePalmEmul();
	nExitCode = stack[1].intValue;
    nRunning = 0;
	v.obj = 0;
	return v;
}

static Var MainWinSetTimerInterval(Var stack[])
{
	Var v;

	globalTimerInterval = stack[1].intValue;
	globalTimerStart = getTimeStamp();
	v.obj = 0;
	return v;
}

//
// Surface
//

#define SURF_MAINWIN 1
#define SURF_IMAGE 2

static WClass *mainWinClass = 0;
static WClass *imageClass = 0;

static int SurfaceGetType(WObject surface)
	{
	WClass *wclass;

	if (surface == 0)
		return 0;

	// cache class pointers for performance
	if (!mainWinClass)
		mainWinClass = getClass(createUtfString("waba/ui/MainWindow"));
	if (!imageClass)
		imageClass = getClass(createUtfString("waba/fx/Image"));

	wclass = WOBJ_class(surface);
	if (compatible(wclass, mainWinClass))
		return SURF_MAINWIN;
	if (compatible(wclass, imageClass))
		return SURF_IMAGE;
	return 0;
	}

//
// Font
//
// var[0] = Class
// var[1] = String name
// var[2] = int size
// var[3] = int style
//

#define WOBJ_FontName(o) (objectPtr(o))[1].obj
#define WOBJ_FontStyle(o) (objectPtr(o))[2].intValue
#define WOBJ_FontSize(o) (objectPtr(o))[3].intValue 
#define Font_PLAIN 0
#define Font_BOLD 1

static FontID getPalmFontID(WObject font)
{
	if (WOBJ_FontStyle(font) == Font_BOLD)
		return boldFont;
	return stdFont;
}

//
// FontMetrics
//
// var[0] = Class
// var[1] = Font
// var[2] = Surface
// var[3] = int ascent
// var[4] = int descent
// var[5] = int leading
//

#define WOBJ_FontMetricsFont(o) (objectPtr(o))[1].obj
#define WOBJ_FontMetricsSurface(o) (objectPtr(o))[2].obj
#define WOBJ_FontMetricsAscent(o) (objectPtr(o))[3].intValue
#define WOBJ_FontMetricsDescent(o) (objectPtr(o))[4].intValue
#define WOBJ_FontMetricsLeading(o) (objectPtr(o))[5].intValue

static Var FontMetricsCreate(Var stack[])
	{
	FontID fontID, prevFontID;
	WObject font, fontMetrics, surface;
	int32 ascent, descent;
	Var v;

	fontMetrics = stack[0].obj;
	font = WOBJ_FontMetricsFont(fontMetrics);
	surface = WOBJ_FontMetricsSurface(fontMetrics);
	if (font == 0 || surface == 0)
		{
		WOBJ_FontMetricsAscent(fontMetrics) = 0;
		WOBJ_FontMetricsDescent(fontMetrics) = 0;
		WOBJ_FontMetricsLeading(fontMetrics) = 0;
		v.obj = 0;
		return v;
		}
	// surface is unused and only 2 fonts are supported
	fontID = getPalmFontID(font);
	prevFontID = FntSetFont(fontID);
	ascent = (int32)FntBaseLine();
	descent = (int32)FntDescenderHeight();
	WOBJ_FontMetricsAscent(fontMetrics) = ascent;
	WOBJ_FontMetricsDescent(fontMetrics) = descent;
	WOBJ_FontMetricsLeading(fontMetrics) = (int32)FntLineHeight() - ascent - descent;
	FntSetFont(prevFontID);
	v.obj = fontMetrics;
	return v;
	}

#define FM_STRINGWIDTH 1
#define FM_CHARARRAYWIDTH 2
#define FM_CHARWIDTH 3

static Var FontMetricsGetWidth(int type, Var stack[])
	{
	FontID fontID, prevFontID;
	WObject font, fontMetrics, surface;
	Var v;
	int32 width;

	fontMetrics = stack[0].obj;
	font = WOBJ_FontMetricsFont(fontMetrics);
	surface = WOBJ_FontMetricsSurface(fontMetrics);
	if (font == 0 || surface == 0)
		{
		v.intValue = 0;
		return v;
		}
	// surface is unused and only 2 fonts are supported
	fontID = getPalmFontID(font);
	prevFontID = FntSetFont(fontID);
	switch (type)
		{
		case FM_CHARWIDTH:
			{
			Char ch;

			ch = (Char)stack[1].intValue;
			width = (int32)FntCharWidth(ch);
			break;
			}
		case FM_STRINGWIDTH:
		case FM_CHARARRAYWIDTH:
			{
			WObject string, charArray;
			int32 start, count;
			uint16 *chars;

			width = 0;
			if (type == FM_STRINGWIDTH)
				{
				string = stack[1].obj;
				if (string == 0)
					break;
				charArray = WOBJ_StringCharArrayObj(string);
				if (charArray == 0)
					break;
				start = 0;
				count = WOBJ_arrayLen(charArray);
				}
			else // FM_CHARARRAYWIDTH
				{
				charArray = stack[1].obj;
				start = stack[2].intValue;
				count = stack[3].intValue;
				if (arrayRangeCheck(charArray, start, count) == 0)
					break; // array null or range invalid
				}
			chars = (uint16 *)WOBJ_arrayStart(charArray);
			chars = &chars[start];
			while (count > 0)
				{
				char buf[40];
				int32 i, n;

				n = sizeof(buf);
				if (n > count)
					n = count;
				for (i = 0; i < n; i++)
					buf[i] = (char)chars[i];
				width += (int32)FntCharsWidth(buf, (Word)count);
				count -= n;
				chars += n;
				}
			break;
			}
		}
	FntSetFont(prevFontID);
	v.intValue = width;
	return v;
	}

static Var FontMetricsGetStringWidth(Var stack[])
	{
	return FontMetricsGetWidth(FM_STRINGWIDTH, stack);
	}

static Var FontMetricsGetCharArrayWidth(Var stack[])
	{
	return FontMetricsGetWidth(FM_CHARARRAYWIDTH, stack);
	}

static Var FontMetricsGetCharWidth(Var stack[])
	{
	return FontMetricsGetWidth(FM_CHARWIDTH, stack);
	}

// Debugging Memory Sizes
#ifdef DEBUGMEMSIZE
static void debugMemSize()
	{
	char buf[40];
	RectangleType rect;

	xmemzero(buf, 40);

	// add class heap info
	buf[xstrlen(buf)] = 'c';
	buf[xstrlen(buf)] = ':';
	StrIToA(&buf[xstrlen(buf)], classHeapUsed);
	buf[xstrlen(buf)] = '/';
	StrIToA(&buf[xstrlen(buf)], classHeapSize);

	buf[xstrlen(buf)] = ' ';

	// add object heap info
	buf[xstrlen(buf)] = 'o';
	buf[xstrlen(buf)] = ':';
	StrIToA(&buf[xstrlen(buf)], heap.memSize - getUnusedMem());
	buf[xstrlen(buf)] = '/';
	StrIToA(&buf[xstrlen(buf)], heap.memSize);

	rect.topLeft.x = 80;
	rect.topLeft.y = 0;
	rect.extent.x = 80;
	rect.extent.y = 15;
	WinEraseRectangle(&rect,0);
	WinDrawChars(buf, xstrlen(buf), 80, 0);
	}
#endif

//
// Image
//
// var[0] = Class
// var[1] = width
// var[2] = height
// var[3] = hook var - winHandle
//

#define WOBJ_ImageWidth(o) (objectPtr(o))[1].intValue
#define WOBJ_ImageHeight(o) (objectPtr(o))[2].intValue
#define WOBJ_ImageWinHandle(o) (objectPtr(o))[3].refValue
#define WOBJ_ImageColormap(o) (objectPtr(o))[4].refValue

static Var ImageFree(Var stack[])
	{
	WObject image;
	Var v;

	image = stack[0].obj;
	ImageDestroy(image);
	WOBJ_ImageWidth(image) = 0;
	WOBJ_ImageHeight(image) = 0;
	v.obj = 0;
	return v;
	}

static void ImageLoadBMP(WObject image, uchar *p);

static Var ImageSetPixels(Var stack[]);

static Var ImageLoad(Var stack[])
	{
	WObject image;
	UtfString path;
	WObject pathString;
	int freeNeeded;
	uchar *p;
	Var v;

	image = stack[0].obj;

	// NOTE: we don't have to free an existing bitmap because this is only called
	// from an image constructor
	WOBJ_ImageWinHandle(image) = NULL;
	WOBJ_ImageColormap(image) = NULL;
	WOBJ_ImageWidth(image) = 0;
	WOBJ_ImageHeight(image) = 0;
	freeNeeded = 0;
	pathString = stack[1].obj;
	v.obj = 0;
	// NOTE: we null terminate here since we call readFileIntoMemory()
	path = stringToUtf(pathString, STU_NULL_TERMINATE | STU_USE_STATIC);
	if (path.len == 0)
		return v;
	// first try from memory
	p = loadFromMem(path.str, path.len, NULL);
	if (p == NULL)
		{
		p = readFileIntoMemory(path.str, 0, NULL);
		if (p == NULL)
			return v;
		freeNeeded = 1;
		}
	ImageLoadBMP(image, p);
	if (freeNeeded)
		xfree(p);
	return v;
	}

static Var ImageCreate(Var stack[])
	{
	WObject image;
	int32 width, height;
	WinHandle imgHandle;
	Word error;
	Var v;

	image = stack[0].obj;
	width = WOBJ_ImageWidth(image);
	height = WOBJ_ImageHeight(image);
	if (width > 0 && height > 0)
		{
		imgHandle = WinCreateOffscreenWindow((SWord)width, (SWord)height,
			screenFormat, &error);
		if (error != 0)
			imgHandle = NULL;
		}
	else
		imgHandle = NULL;
	WOBJ_ImageWinHandle(image) = imgHandle;
	v.obj = 0;
	return v;
	}

static void ImageDestroy(WObject image)
	{
	WinHandle winHandle;
    GrColorTableP pColormap;

    winHandle = WOBJ_ImageWinHandle(image);
    pColormap = WOBJ_ImageColormap(image);

    if (pColormap)
    {
        int i;
        for (i=1; i<=GR_CTABLE_SIZE(pColormap); ++i)
            GrFreeColor(pColormap[i]);
        free(pColormap);
        WOBJ_ImageColormap(image) = NULL;
    }

	if (winHandle)
    {
	    WinDeleteWindow(winHandle, false);
	    WOBJ_ImageWinHandle(image) = NULL;
    }
	}

static void drawScanline
(
    int y,
    int width,
    int bpp,
    uchar *p,
    GrColorTableP arpColors
)
	{
	int mask, curMask, firstBit, step;
	int byt, bit, x, x1, x2;

    GrColor* pColors = arpColors + 1;
        // skip past the "number of colors" entry

	if (bpp == 1)
		{
		mask = 0x80;
		firstBit = 7;
		step = 1;
		}
	else if (bpp == 4)
		{
		mask = 0xF0;
		firstBit = 4;
		step = 4;
		}
	else // bpp == 8
		{
		mask = 0xFF;
		firstBit = 0;
		step = 8;
		}
	bit = firstBit;
    curMask = mask;
	byt = *p++;
	x1 = -1;
	x2 = -1;
	x = 0;
	while (1)
		{
        int nColormapIndex = (curMask & byt) >> bit;
        int nColorEntry = pColors[nColormapIndex];
        GrPlot(x,y,nColorEntry);
#ifdef DEBUGVERBOSE
        fprintf(pfilLog, "(%d,%d,%d)\n", x, y, nColorEntry);
        fflush(pfilLog);
#endif
		if (++x >= width)
			break;
		if (bit == 0)
			{
			bit = firstBit;
            curMask = mask;
			byt = *p++;
			}
		else
        {
			bit -= step;
            curMask >>= step;
        }
		}
	}

static Var ImageSetPixels(Var stack[])
	{
	WObject image, colorMapArray, pixelsArray;
	int32 i, j, bpp, bytesPerRow, numRows, y, numColors, imageWidth;
	uint32 *colorMap;
	WinHandle imgHandle, oldWinHandle;
    GrColorTableP pColorTable;
	RectangleType rect;
	uchar *p;
	Var v;

	v.obj = 0;
	image = stack[0].obj;
	bpp = stack[1].intValue;
	colorMapArray = stack[2].obj;
	bytesPerRow = stack[3].intValue;
	numRows = stack[4].intValue;
	y = stack[5].intValue;
	pixelsArray = stack[6].obj;

	// validate parameters
	imgHandle = WOBJ_ImageWinHandle(image);
	if (imgHandle == NULL)
		return v;
	imageWidth = WOBJ_ImageWidth(image);
	if ((bytesPerRow * 8) / bpp < imageWidth)
		return v;
	if (colorMapArray == 0 || pixelsArray == 0)
		return v;
	numColors = WOBJ_arrayLen(colorMapArray);
	if (bpp != 1 && bpp != 4 && bpp != 8)
		return v;
	if (bpp == 1 && numColors == 2)
		;
	else if (bpp == 4 && numColors == 16)
		;
	else if (bpp == 8 && numColors == 256)
		;
	else
		return v;
	if (WOBJ_arrayLen(pixelsArray) < bytesPerRow * numRows)
		return v;
	oldWinHandle = WinGetDrawWindow();
	WinSetDrawWindow(imgHandle);
	rect.topLeft.x = 0;
	rect.topLeft.y = y;
	rect.extent.x = imageWidth;
	rect.extent.y = numRows;

	colorMap = (uint32 *)WOBJ_arrayStart(colorMapArray);
    pColorTable = xmalloc(sizeof(int) * GR_CTABLE_ALLOCSIZE(numColors));
    pColorTable[0] = numColors;
    j = 1;
	for (i = 0; i < numColors; i++)
		{
		int nb = colorMap[i] & 0xFF;
		int ng = (colorMap[i] >> 8) & 0xFF;
		int nr = (colorMap[i] >> 16) & 0xFF;
        pColorTable[j++] = GrAllocColor(nr,ng,nb);
		}

	WOBJ_ImageColormap(image) = pColorTable;

	p = (uchar *)WOBJ_arrayStart(pixelsArray);
	for (i = 0; i < numRows; i++)
		{
		drawScanline(y + i, imageWidth, bpp, p, pColorTable);
		p += bytesPerRow;
		}
	WinSetDrawWindow(oldWinHandle);
	}

/*
static void ImageLoadBMP(WObject image, uchar *p)
	{
    uint32 width, height;
	RectangleType rect;
	Word error;
	WinHandle imgHandle, oldWinHandle;
    GrBmpImage* pBmp;
    GrPattern* pPattern;
    int fh, bytesread;

    pBmp = GrLoadBmpImage(p);
    if (!pBmp) return;

    GrAllocBmpImageColors(pBmp, NULL);
    pPattern = GrConvertBmpImageToPattern(pBmp);
    if (!pPattern) return;
    width = GrBmpImageWidth(pBmp);
    height = GrBmpImageHeight(pBmp);

	imgHandle = WinCreateOffscreenWindow((SWord)width, (SWord)height,
		screenFormat, &error);
	if (error != 0)
		return;

	WOBJ_ImageWinHandle(image) = imgHandle;
	WOBJ_ImageWidth(image) = width;
	WOBJ_ImageHeight(image) = height;
	WOBJ_ImagePattern(image) = pPattern;
	WOBJ_ImageBMP(image) = pBmp;

	oldWinHandle = WinGetDrawWindow();
	WinSetDrawWindow(imgHandle);
	rect.topLeft.x = 0;
	rect.topLeft.y = 0;
	rect.extent.x = width;
	rect.extent.y = height;
    GrImageDisplay(0,0,GrImageFromPattern(pPattern));
	WinSetDrawWindow(oldWinHandle);
	}
*/
// Intel-architecture getUInt32 - these are not #defines to make the executable smaller

static uint32 inGetUInt32(uchar *b)
	{
	return (uint32)( (uint32)b[3]<<24 | (uint32)b[2]<<16 | (uint32)b[1]<<8 | (uint32)b[0] );
	}

static uint32 inGetUInt16(uchar *b)
	{
	return (uint32)( (uint16)b[1]<<8 | (uint16)b[0] );
	}

static void ImageLoadBMP(WObject image, uchar *p)
	{
	uint32 bitmapOffset, infoSize, width, height, bpp;
	uint32 compression, numColors, scanlen;
	int i, y;
	uchar *ip, *b;
    int nr, ng, nb;
	RectangleType rect;
	WinPtr winPtr;
	Word error;
	WinHandle imgHandle, oldWinHandle;
    GrColorTableP pColorTable;

	// header (54 bytes)
	// 0-1   magic chars 'BM'
	// 2-5   uint32 filesize (not reliable)
	// 6-7   uint16 0
	// 8-9   uint16 0
	// 10-13 uint32 bitmapOffset
	// 14-17 uint32 info size
	// 18-21 int32  width
	// 22-25 int32  height
	// 26-27 uint16 nplanes
	// 28-29 uint16 bits per pixel
	// 30-33 uint32 compression flag
	// 34-37 uint32 image size in bytes
	// 38-41 int32  biXPelsPerMeter
	// 32-45 int32  biYPelsPerMeter
	// 46-49 uint32 colors used
	// 50-53 uint32 important color count

	if (p[0] != 'B' || p[1] != 'M')
		return; // not a BMP file
	bitmapOffset = inGetUInt32(&p[10]);
	infoSize = inGetUInt32(&p[14]);
	if (infoSize != 40)
		return; // old-style BMP
	width = inGetUInt32(&p[18]);
	height = inGetUInt32(&p[22]);
	if (width > 65535 || height > 65535)
		return; // bad width/height
	bpp = inGetUInt16(&p[28]);
	if (bpp != 1 && bpp != 4 && bpp != 8)
		return; // not a 2, 16 or 256 color image
	compression = inGetUInt32(&p[30]);
	if (compression != 0)
		return; // compressed image

	numColors = inGetUInt32(&p[46]);
    if (!numColors)
        numColors = 1 << bpp;
	scanlen = (width * bpp + 7) / 8; // # bytes
	scanlen = ((scanlen + 3) / 4) * 4; // end on 32 bit boundry

	// colormap
	//
	// 0-3 uint32 col[0]
	// 4-7 uint32 col[1]
	// ...
    pColorTable = xmalloc(sizeof(int) * GR_CTABLE_ALLOCSIZE(numColors));
    pColorTable[0] = numColors;
	b = (uchar*) &p[54];
	for (i=1; i<=numColors; ++i)
		{
            int nMapEntry;

            nb = *b++;
            ng = *b++;
            nr = *b++;
            b++;
                // reserved

            nMapEntry = GrAllocColor(nr,ng,nb);
            pColorTable[i] = nMapEntry;
#ifdef DEBUGVERBOSE
            fprintf(pfilLog, "Color %d is %d (%d,%d,%d)\n", i, nMapEntry, nr, ng, nb);
            fflush(pfilLog);
#endif
		}

	imgHandle = WinCreateOffscreenWindow((SWord)width, (SWord)height,
		screenFormat, &error);
	if (error != 0)
		return;
	WOBJ_ImageWinHandle(image) = imgHandle;
	WOBJ_ImageWidth(image) = width;
	WOBJ_ImageHeight(image) = height;
	WOBJ_ImageColormap(image) = pColorTable;

	oldWinHandle = WinGetDrawWindow();
	WinSetDrawWindow(imgHandle);
	rect.topLeft.x = 0;
	rect.topLeft.y = 0;
	rect.extent.x = width;
	rect.extent.y = height;
	ip = &p[bitmapOffset];

#ifdef DEBUGVERBOSE
    fprintf(pfilLog, "About to draw bitmap: %d, %d, %d, %d\n", numColors, scanlen, width, bitmapOffset);
    fflush(pfilLog);
#endif

	for (y = (int)height - 1; y >= 0; y--)
		{
		drawScanline(y, width, bpp, ip, pColorTable);
		ip += scanlen;
		}
	WinSetDrawWindow(oldWinHandle);
	}

//
// Graphics
//
// var[0] = Class
// var[1] = Surface
// var[2] = hook var - 1 for window surface and 2 for image
// var[3] = hook var - rgb
// var[4] = hook var - has clip
// var[5] = hook var - clipX
// var[6] = hook var - clipY
// var[7] = hook var - clipWidth
// var[8] = hook var - clipHeight
// var[9] = hook var - PALM Font ID
// var[10] = hook var - drawing op
// var[11] = hook var - x translation
// var[12] = hook var - y translation
//

#define WOBJ_GraphicsSurface(o) (objectPtr(o))[1].obj
#define WOBJ_GraphicsSurfType(o) (objectPtr(o))[2].intValue
#define WOBJ_GraphicsRGB(o) (objectPtr(o))[3].intValue
#define WOBJ_GraphicsHasClip(o) (objectPtr(o))[4].intValue
#define WOBJ_GraphicsClipX(o) (objectPtr(o))[5].intValue
#define WOBJ_GraphicsClipY(o) (objectPtr(o))[6].intValue
#define WOBJ_GraphicsClipWidth(o) (objectPtr(o))[7].intValue
#define WOBJ_GraphicsClipHeight(o) (objectPtr(o))[8].intValue
#define WOBJ_GraphicsFontID(o) (objectPtr(o))[9].refValue
#define WOBJ_GraphicsDrawOp(o) (objectPtr(o))[10].intValue
#define WOBJ_GraphicsTransX(o) (objectPtr(o))[11].intValue
#define WOBJ_GraphicsTransY(o) (objectPtr(o))[12].intValue

#define DRAW_OVER 1
#define DRAW_AND 2
#define DRAW_OR 3
#define DRAW_XOR 4

#define GR_FILLRECT   0
#define GR_DRAWLINE   1
#define GR_FILLPOLY   2
#define GR_DRAWCHARS  3
#define GR_DRAWSTRING 4
#define GR_DOTS       5
#define GR_COPYRECT   6
#define GR_DRAWCURSOR 7

static Var GraphicsCreate(Var stack[])
	{
	Var v;
	WObject gr, surface;

	gr = stack[0].obj;
	surface = WOBJ_GraphicsSurface(gr);
	WOBJ_GraphicsSurfType(gr) = SurfaceGetType(surface);
	WOBJ_GraphicsRGB(gr) = 0;
	WOBJ_GraphicsHasClip(gr) = 0;
	WOBJ_GraphicsFontID(gr) = (void *)stdFont;
	WOBJ_GraphicsDrawOp(gr) = DRAW_OVER;
	WOBJ_GraphicsTransX(gr) = 0;
	WOBJ_GraphicsTransY(gr) = 0;
	v.obj = 0;
	return v;
	}

#define GraphicsFree Return0Func

static Var GraphicsSetFont(Var stack[])
	{
	WObject gr, font;
	Var v;

	gr = stack[0].obj;
	font = stack[1].obj;
	WOBJ_GraphicsFontID(gr) = (void *)getPalmFontID(font);
	v.obj = 0;
	return v;
	}

static Var GraphicsSetColor(Var stack[])
	{
	Var v;
	WObject gr;
	int32 r, g, b;

	gr = stack[0].obj;
	r = stack[1].intValue;
	g = stack[2].intValue;
	b = stack[3].intValue;
	WOBJ_GraphicsRGB(gr) = (r & 0xFF) << 16 | ((g & 0xFF) << 8) | (b & 0xFF);
	v.obj = 0;
	return v;
	}

static Var GraphicsSetDrawOp(Var stack[])
	{
	Var v;
	WObject gr;
	int32 op;

	gr = stack[0].obj;
	op = stack[1].intValue;
	WOBJ_GraphicsDrawOp(gr) = op;
	v.obj = 0;
	return v;
	}

static Var GraphicsSetClip(Var stack[])
	{
	WObject gr;
	int32 transX, transY;
	Var v;


	gr = stack[0].obj;
	transX = WOBJ_GraphicsTransX(gr);
	transY = WOBJ_GraphicsTransY(gr);
	WOBJ_GraphicsHasClip(gr) = 1;
	// clip X and Y are stored in absolute coordinates
	WOBJ_GraphicsClipX(gr) = stack[1].intValue + transX;
	WOBJ_GraphicsClipY(gr) = stack[2].intValue + transY;
	WOBJ_GraphicsClipWidth(gr) = stack[3].intValue;
	WOBJ_GraphicsClipHeight(gr) = stack[4].intValue;
	v.obj = 0;
	return v;
	}

static Var GraphicsGetClip(Var stack[])
	{
	WObject gr, rect;
	Var v;

	v.obj = 0;
	gr = stack[0].obj;
	rect = stack[1].obj;
	if (rect == 0 || WOBJ_GraphicsHasClip(gr) != 1)
		return v;
	WOBJ_RectX(rect) = WOBJ_GraphicsClipX(gr) - WOBJ_GraphicsTransX(gr);
	WOBJ_RectY(rect) = WOBJ_GraphicsClipY(gr) - WOBJ_GraphicsTransY(gr);
	WOBJ_RectWidth(rect) = WOBJ_GraphicsClipWidth(gr);
	WOBJ_RectHeight(rect) = WOBJ_GraphicsClipHeight(gr);
	v.obj = rect;
	return v;
	}

static Var GraphicsClearClip(Var stack[])
	{
	WObject gr;
	Var v;

	gr = stack[0].obj;
	WOBJ_GraphicsHasClip(gr) = 0;
	v.obj = 0;
	return v;
	}

static Var GraphicsTranslate(Var stack[])
	{
	WObject gr;
	Var v;

	gr = stack[0].obj;
	WOBJ_GraphicsTransX(gr) += stack[1].intValue;
	WOBJ_GraphicsTransY(gr) += stack[2].intValue;
	v.obj = 0;
	return v;
	}

static Var GraphicsDraw(int type, Var stack[])
	{
	WObject gr, surface;
	int32 surfaceType, drawOp, transX, transY;
	uint32 rgb;
	WinHandle winHandle, oldWinHandle;
	RectangleType oldClip;
	Var v;

	v.obj = 0;
	gr = stack[0].obj;
	rgb = WOBJ_GraphicsRGB(gr);
	if (rgb!=0 && !getVMIsColor())
	{
		int r, g, b;
		
		r = rgb >> 16;
		g = (rgb >> 8) & 0xFF;
		b = rgb & 0xFF;
		if (r < 127 || g < 127 || b < 127)
			rgb = 0;
		else
			rgb = 1;
	}
    else
    {
        setCurrentColor(rgb);
    }

	surface = WOBJ_GraphicsSurface(gr);
	surfaceType = WOBJ_GraphicsSurfType(gr);
	if (surfaceType == SURF_MAINWIN)
		winHandle = WinGetDrawWindow();
	else if (surfaceType == SURF_IMAGE)
		{
		oldWinHandle = WinGetDrawWindow();
		winHandle = WOBJ_ImageWinHandle(surface);
		if (winHandle == NULL)
			return v;
		WinSetDrawWindow(winHandle);
		}
	else
		return v;

	// set clip if one exists
	if (WOBJ_GraphicsHasClip(gr) != 0)
		{
		RectangleType rect;

		WinGetClip(&oldClip);
		rect.topLeft.x = WOBJ_GraphicsClipX(gr);
		rect.topLeft.y = WOBJ_GraphicsClipY(gr);
		rect.extent.x = WOBJ_GraphicsClipWidth(gr);
		rect.extent.y = WOBJ_GraphicsClipHeight(gr);
		WinClipRectangle(&rect);
		WinSetClip(&rect);
		}

	transX = WOBJ_GraphicsTransX(gr);
	transY = WOBJ_GraphicsTransY(gr);

	drawOp = WOBJ_GraphicsDrawOp(gr);
	// NOTE: only DRAW_OVER is supported for drawing lines, rectangles and
	// text under PalmOS so drawOp is only used for GR_COPYRECT
	switch(type)
		{
		case GR_FILLRECT:
			{
			RectangleType rect;

			rect.topLeft.x = stack[1].intValue + transX;
			rect.topLeft.y = stack[2].intValue + transY;
			rect.extent.x = stack[3].intValue;
			rect.extent.y = stack[4].intValue;
			if (rect.extent.x <= 0 || rect.extent.y <= 0)
				break;
			if (rgb==1 && !getVMIsColor())
				WinEraseRectangle(&rect, 0);
			else
				WinDrawRectangle(&rect, 0);
			break;
			}
		case GR_DRAWLINE:
			{
			if (rgb==1 && !getVMIsColor())
				WinEraseLine(stack[1].intValue + transX, stack[2].intValue + transY,
					stack[3].intValue + transX, stack[4].intValue + transY);
			else
				WinDrawLine(stack[1].intValue + transX, stack[2].intValue + transY,
					stack[3].intValue + transX, stack[4].intValue + transY);
			break;
			}
		case GR_FILLPOLY:
			{
			WObject xArray, yArray;
			int32 i, count, *x, *y;
//            int *pnPts ; 
            int (*pnPts)[2] ;
            int *pnCurPt;
            int32* arnPts[2];

			xArray = stack[1].obj;
			yArray = stack[2].obj;
			if (xArray == 0 || yArray == 0)
				break;
			x = (int32 *)WOBJ_arrayStart(xArray);
			y = (int32 *)WOBJ_arrayStart(yArray);
			count = stack[3].intValue;
			if (count < 3 || count > WOBJ_arrayLen(xArray) ||
				count > WOBJ_arrayLen(yArray))
				break;

            pnPts = xmalloc(count*sizeof(int)*2);
//            pnCurPt = pnPts;
            pnCurPt = pnPts[0];
            for (i=0; i<count; ++i)
            {
                *pnCurPt++ = x[i];
                *pnCurPt++ = y[i];
            }
            GrFilledPolygon(count, pnPts, getCurrentColor());
            xfree(pnPts);
			break;
			}
		case GR_DRAWCHARS:
		case GR_DRAWSTRING:
			{
			WObject string, charArray;
			int32 start, count;
			uint16 *chars;
			int32 x, y, i, n;
			FontID fontID, prevFontID;
			char buf[40];

			if (type == GR_DRAWSTRING)
				{
				string = stack[1].obj;
				if (string == 0)
					break;
				x = stack[2].intValue;
				y = stack[3].intValue;
				charArray = WOBJ_StringCharArrayObj(string);
				if (charArray == 0)
					break;
				start = 0;
				count = WOBJ_arrayLen(charArray);
				}
			else
				{
				charArray = stack[1].obj;
				start = stack[2].intValue;
				count = stack[3].intValue;
				x = stack[4].intValue;
				y = stack[5].intValue;
				if (arrayRangeCheck(charArray, start, count) == 0)
					break; // array null or range invalid
				}
			chars = (uint16 *)WOBJ_arrayStart(charArray);
			chars = &chars[start];
			x += transX;
			y += transY;
			fontID = (FontID)WOBJ_GraphicsFontID(gr);
			prevFontID = FntSetFont(fontID);
			while (count > 0)
				{
				n = sizeof(buf);
				if (n > count)
					n = count;
				for (i = 0; i < n; i++)
					buf[i] = (char)chars[i];
				if (rgb==1 && !getVMIsColor())
					WinEraseChars(buf, (int16) n, x, y);
				else
					WinDrawChars(buf, (int16) n, x, y);
				x += FntCharsWidth(buf, (Word)n);
				count -= n;
				chars += n;
				}
			FntSetFont(prevFontID);
			break;
			}
		case GR_DOTS:
			{
			int32 x1, y1, x2, y2;
			int32 x, y;

			x1 = stack[1].intValue + transX;
			y1 = stack[2].intValue + transY;
			x2 = stack[3].intValue + transX;
			y2 = stack[4].intValue + transY;
			if (x1 == x2)
				{
				// vertical
				if (y1 > y2)
					{
					y = y1;
					y1 = y2;
					y2 = y;
					}
				// NOTE: I tried WinFillLine() and could never get it to draw
				// just the pixels I wanted.
				for (; y1 <= y2; y1 += 2)
					{
					if (rgb==1 && !getVMIsColor())
						WinEraseLine(x1, y1, x1, y1);
					else
						WinDrawLine(x1, y1, x1, y1);
					}
				}
			else if (y1 == y2)
				{
				// horitzontal
				if (x1 > x2)
					{
					x = x1;
					x1 = x2;
					x2 = x;
					}
				for (; x1 <= x2; x1 += 2)
					{
					if (rgb==1 && !getVMIsColor())
						WinEraseLine(x1, y1, x1, y1);
					else
						WinDrawLine(x1, y1, x1, y1);
					}
				}
			break;
			}
		case GR_COPYRECT:
			{
			WObject srcSurf;
			RectangleType rect;
			int32 dstX, dstY;
			int srcSurfaceType;
			WinHandle srcWinHandle;
			ScrOperation mode;

			srcSurf = stack[1].obj;
			rect.topLeft.x = stack[2].intValue;
			rect.topLeft.y = stack[3].intValue;
			rect.extent.x = stack[4].intValue;
			rect.extent.y = stack[5].intValue;
			dstX = stack[6].intValue + transX;
			dstY = stack[7].intValue + transY;
			if (srcSurf == 0)
				break;
			// convert op to native op
			if (drawOp == DRAW_OVER)
				mode = scrCopy;
			else if (drawOp == DRAW_XOR)
				mode = scrXOR;
			else if (drawOp == DRAW_AND)
				mode = scrAND;
			else if (drawOp == DRAW_OR)
				mode = scrOR;
			if (surface == srcSurf)
				srcWinHandle = winHandle;
			else
				{
				srcSurfaceType = SurfaceGetType(srcSurf);
				srcWinHandle = 0;
				if (srcSurfaceType == SURF_MAINWIN)
					srcWinHandle = WinGetDrawWindow();
				else if (srcSurfaceType == SURF_IMAGE)
					srcWinHandle = WOBJ_ImageWinHandle(srcSurf);
				}
			if (srcWinHandle != NULL)
				WinCopyRectangle(srcWinHandle, winHandle, &rect, dstX, dstY, mode);
			break;
			}
		case GR_DRAWCURSOR:
			{
			int32 dstX, dstY;
			RectangleType rect;
			WinHandle imgHandle;
			Word error;

			dstX = stack[1].intValue + transX;
			dstY = stack[2].intValue + transY;
			rect.topLeft.x = dstX;
			rect.topLeft.y = dstY;
			rect.extent.x = stack[3].intValue;
			rect.extent.y = stack[4].intValue;
#ifdef DEBUGVERBOSE
            fprintf(pfilLog, "Before drawcursor\n");
            fflush(pfilLog);
#endif
			DrawCursor(&rect);
#ifdef DEBUGVERBOSE
            fprintf(pfilLog, "After drawcursor\n");
            fflush(pfilLog);
#endif
			break;
			}
		}
	if (WOBJ_GraphicsHasClip(gr) != 0)
		WinSetClip(&oldClip);
	if (surfaceType == SURF_IMAGE)
		WinSetDrawWindow(oldWinHandle);
	return v;
	}

static Var GraphicsFillRect(Var stack[])
	{
	return GraphicsDraw(GR_FILLRECT, stack);
	}

static Var GraphicsDrawLine(Var stack[])
	{
	return GraphicsDraw(GR_DRAWLINE, stack);
	}

static Var GraphicsFillPolygon(Var stack[])
	{
	return GraphicsDraw(GR_FILLPOLY, stack);
	}

static Var GraphicsDrawChars(Var stack[])
	{
	return GraphicsDraw(GR_DRAWCHARS, stack);
	}

static Var GraphicsDrawString(Var stack[])
	{
	return GraphicsDraw(GR_DRAWSTRING, stack);
	}

static Var GraphicsDrawDots(Var stack[])
	{
	return GraphicsDraw(GR_DOTS, stack);
	}

static Var GraphicsCopyRect(Var stack[])
	{
	return GraphicsDraw(GR_COPYRECT, stack);
	}

static Var GraphicsDrawCursor(Var stack[])
	{
	return GraphicsDraw(GR_DRAWCURSOR, stack);
	}

//
// File
//
// var[0] = Class
// var[1] = String name
// var[2] = int mode
// var[3] = hook var - file handle

#define WOBJ_FileName(o) (objectPtr(o))[1].obj
#define WOBJ_FileMode(o) (objectPtr(o))[2].intValue
#define WOBJ_FileHandle(o) (objectPtr(o))[3].intValue

#define File_DONT_OPEN 0
#define File_READ_ONLY 1
#define File_WRITE_ONLY 2
#define File_READ_WRITE 3
#define File_CREATE 4

static char* _FileAllocStr(WObject string, char *add)
	{
	WObject charArray;
	char *s;
	int32 len, addLen, i, j;
	int16 *chars;

	if (string == 0)
		return NULL;
	charArray = WOBJ_StringCharArrayObj(string);
	if (charArray == 0)
		return NULL;
	if (add != NULL)
		addLen = strlen(add);
	else
		addLen = 0;
	len = WOBJ_arrayLen(charArray);
	chars = (int16 *)WOBJ_arrayStart(charArray);
	s = (char *)xmalloc((len + addLen + 1) * sizeof(char));
	if (s == NULL)
		return NULL;
	j = 0;
	for (i = 0; i < len; i++)
		s[j++] = (char)chars[i];
	for (i = 0; i < addLen; i++)
		s[j++] = add[i];
	s[j] = 0;
	return s;
	}

static void _FileFreeStr(char* path)
	{
	if (path != NULL)
		xfree((void *)path);
	}

static int32 _FileClose(WObject file)
	{
	int fileH;

	fileH = WOBJ_FileHandle(file);
	if (fileH != -1)
		close(fileH);
	WOBJ_FileHandle(file) = -1;
	return 1;
	}

static Var FileCreate(Var stack[])
	{
	WObject file;
	Var v;
	int32 fileMode;
	int rwMode;
	int createDis;
	char* path;
	int fileH;

	file = stack[0].obj;
	v.obj = 0;
	fileMode = WOBJ_FileMode(file);
	rwMode = 0;
	if (fileMode == File_READ_ONLY)
		rwMode = O_RDONLY;
	else if (fileMode == File_WRITE_ONLY)
		rwMode = O_WRONLY;
	else if (fileMode == File_READ_WRITE)
		rwMode = O_RDWR;
    else if (fileMode == File_CREATE)
        rwMode = O_CREAT|O_WRONLY|O_TRUNC;

    rwMode |= O_BINARY;

	WOBJ_FileHandle(file) = -1;
	if (fileMode == File_DONT_OPEN)
		return v;
	path = _FileAllocStr(WOBJ_FileName(file), NULL);
	if (path == NULL)
		return v;

#ifdef DEBUGVERBOSE
    print("In FileCreate()");
    print(path);
#endif
    fileH = open(path, rwMode, S_IREAD | S_IWRITE);
	_FileFreeStr(path);
	WOBJ_FileHandle(file) = fileH;
#ifdef DEBUGVERBOSE
    print("Leaving FileCreate()");
#endif
    return v;
	}

static void FileDestroy(WObject file)
	{
	_FileClose(file);
	}

static Var FileIsOpen(Var stack[])
	{
	WObject file;
	int fileH;
	Var v;

    file = stack[0].obj;
	fileH = WOBJ_FileHandle(file);
	v.intValue = 0;
	if (fileH == -1)
		return v;
	v.intValue = 1;
	return v;
	}

static Var FileGetLength(Var stack[])
	{
	WObject file;
	int fileH;
	int len;
	Var v;
    struct stat st;

	file = stack[0].obj;
	fileH = WOBJ_FileHandle(file);
	v.intValue = 0;
	if (fileH == -1)
		return v;

    if (fstat(fileH, &st) != 0)
        return v;

    len = st.st_size;
    v.intValue = len;
	return v;
	}

#define FILE_CREATE_DIR 1
#define FILE_IS_DIR 2
#define FILE_DELETE 3
#define FILE_RENAME 4
#define FILE_EXISTS 5

static Var FileOp(Var stack[], int op)
	{
	WObject file;
	char* path;
	Var v;

	v.intValue = 0;
	file = stack[0].obj;
	path = _FileAllocStr(WOBJ_FileName(file), NULL);
	if (path == NULL)
		return v;
	switch (op)
		{
		case FILE_CREATE_DIR:
			{
			if (mkdir(path, S_IREAD | S_IWRITE) == 0)
				v.intValue = 1;
			break;
			}
		case FILE_IS_DIR:
			{
            struct stat st;
            if
            (
                stat(path, &st) == 0
                &&
                S_ISDIR(st.st_mode)
            )
            {
                v.intValue = 1;
            }
			break;
			}
		case FILE_DELETE:
			{
			int isDir;
			int attr;

			_FileClose(file);
            if (remove(path) == 0)
                v.intValue = 1;
			break;
			}
		case FILE_RENAME:
			{
			char* dstPath;

			dstPath = _FileAllocStr(stack[1].obj, NULL);
			if (dstPath != NULL)
				{
				_FileClose(file);
				if (rename(path, dstPath) == 0)
					v.intValue = 1;
				_FileFreeStr(dstPath);
				}
			break;
			}
		case FILE_EXISTS:
			{
            struct stat st;
            if (stat(path, &st) == 0)
                v.intValue = 1;
			break;
			}
		}
	_FileFreeStr(path);
	return v;
	}

static Var FileCreateDir(Var stack[])
	{
	return FileOp(stack, FILE_CREATE_DIR);
	}

static Var FileIsDir(Var stack[])
	{
	return FileOp(stack, FILE_IS_DIR);
	}

static Var FileDelete(Var stack[])
	{
	return FileOp(stack, FILE_DELETE);
	}

static Var FileRename(Var stack[])
	{
	return FileOp(stack, FILE_RENAME);
	}

static Var FileExists(Var stack[])
	{
	return FileOp(stack, FILE_EXISTS);
	}

static Var FileSeek(Var stack[])
	{
	WObject file;
	int32 pos;
	int fileH;
	Var v;

	v.intValue = 0;
	file = stack[0].obj;
	pos = stack[1].intValue;
	fileH = WOBJ_FileHandle(file);
	if (fileH == -1)
		return v;
	if (lseek(fileH, pos, SEEK_SET) == (int)pos)
		v.intValue = 1;
	return v;
	}

static Var FileClose(Var stack[])
	{
	Var v;

	v.intValue = _FileClose(stack[0].obj);
	return v;
	}

typedef struct FileListItemStruct
	{
	char *fileName;
	struct FileListItemStruct *next;
	} FileListItem;

static Var FileListDir(Var stack[])
	{
	WObject file, stringArray, *strings;
	char *path;
    struct dirent *dp;
    DIR *dirp;
	char *fileName;
	FileListItem *list, *item;
	int i, numItems;
	Var v;

	v.obj = 0;
	file = stack[0].obj;
	path = _FileAllocStr(WOBJ_FileName(file), NULL);
	if (path == NULL)
		return v;

	// read paths into linked list
    dirp = opendir(path);
	_FileFreeStr(path);
	if (dirp == NULL)
		return v;
	list = NULL;
	numItems = 0;
    while ((dp = readdir(dirp)) != NULL) {
		fileName = dp->d_name;
		if ((fileName[0] == '.' && fileName[1] == 0) ||
			(fileName[0] == '.' && fileName[1] == '.' && fileName[2] == 0))
			continue;
		item = (FileListItem *)xmalloc(sizeof(FileListItem));
		if (item == NULL)
			break;
		item->fileName = malloc((strlen(fileName) + 1) * sizeof(char));
		if (item->fileName == NULL)
			{
			free(item);
			break;
			}
		strcpy(item->fileName, fileName);
		item->next = list;
		list = item;
		numItems++;
		}
	closedir(dirp);

	// convert linked list into string array
	stringArray = createArrayObject(1, numItems);
	if (pushObject(stringArray) == -1)
		goto freereturn;
	i = numItems - 1;
	item = list;
	while (item)
		{
		// we need to recompute the start pointer each iteration
		// in case garbage collection during a string create causes
		// memory to move around
		strings = (WObject *)WOBJ_arrayStart(stringArray);
		strings[i--] = createString(item->fileName);
		item = item->next;
		}
	popObject(); // stringArray

freereturn:
	// free linked list
	while (list)
		{
		item = list;
		list = list->next;
		xfree(item->fileName);
		xfree(item);
		}

	v.obj = stringArray;
	return v;
	}

static Var FileReadWriteBytes(Var stack[], int isRead)
	{
	WObject file, byteArray;
	int32 start, count;
	uchar *bytes;
	int fileH;
	int numRW;
	Var v;
    int i;

	v.intValue = -1;
	file = stack[0].obj;
	fileH = WOBJ_FileHandle(file);
	if (fileH == -1)
		return v;
	byteArray = stack[1].obj;
	start = stack[2].intValue;
	count = stack[3].intValue;
	if (arrayRangeCheck(byteArray, start, count) == 0)
		return v; // array null or range invalid
	bytes = (uchar *)WOBJ_arrayStart(byteArray);


#ifdef DEBUGVERBOSE
    fprintf(pfilLog, "count = %d\n", count);
#endif

    if (isRead)
        numRW = read(fileH, &bytes[start], count);
    else
        numRW = write(fileH, &bytes[start], count);


#ifdef DEBUGVERBOSE
    for (i=0; i<count; ++i)
        fprintf(pfilLog, "%d ", (int) bytes[start+i]);
#endif

	v.intValue = numRW;
	return v;
	}

static Var FileRead(Var stack[])
	{
	return FileReadWriteBytes(stack, 1);
	}

static Var FileWrite(Var stack[])
	{
	return FileReadWriteBytes(stack, 0);
	}

//
// Socket 
//
#include "tcp/tcp-ip.c"
// var[0] = Class id
// var[1] = int socket_id

#define WOBJ_socket_id(o) (objectPtr(o))[1].intValue 
					//    (objectPtr(o))[1].obj

static net_started = 0 ;

/*************************************************************************
                               class waba/io/Socket
*************************************************************************/

struct  sockaddr_in         serv_addr; // 1 seul hote à la fois pour l'instant

/* implementation of native waba/io/Socket._nativeCreate() */
Var SocketCreate(Var stack[]) {
    Var v; 
    WObject sockWO, host;
    UtfString hostname;
    unsigned long ipAddr;
    int port, sock;
    struct  hostent         *server;
	UtfString s;
    
    v.obj = 0;
    sockWO = stack[0].obj;
    host = stack[1].obj;
    port = stack[2].obj;

    _sock_init() ;
/*
if(!net_started) marche pas comm prévu, on y passe jamais
	{
    _sock_init() ;
    net_started = 1 ;
	}
*/

if((sock=socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
    perror("socket");
	return v;
    }

s = stringToUtf(host, STU_NULL_TERMINATE | STU_USE_STATIC);
    
if(!(server=gethostbyname(s.str)))
    {
    printf("Erreur DNS\n");
	return v;
	}

//xmemzero(saddr, sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr = *(struct in_addr*)server->h_addr;
serv_addr.sin_port = htons(port);

if (connect(sock, &serv_addr, sizeof(serv_addr)) < 0)
    {
	close(sock);
	free(&serv_addr);
	return v;
	}
WOBJ_socket_id(sockWO) = sock;
return v;
}

/* implementation of function for closing a socket */
int32 mySocketClose( WObject socket ) {

    int sockfd;
    sockfd = WOBJ_socket_id(socket);
	close_s(sockfd);
    return 1;
}

/* implementation of the function called by the garbage collector when a
   Socket object is destroyed                                             */
void SocketDestroy( WObject socket ) {

    mySocketClose( socket );

}

/* implementation of native waba/io/Socket.close() */
Var WBFSocketClose( Var stack[] ) {
    Var v;
    WObject socket;

    v.obj = 0;
    socket = stack[0].obj;
    v.intValue = mySocketClose(socket);

    return v;

}

/* implementation of native waba/io/Socket.isOpen() */
Var SocketIsOpen( Var stack[] ) {
    Var v; 
    struct stat buf;
    WObject socket;
    int sockfd;

    v.obj = 0;
    socket = stack[0].obj;
    sockfd = WOBJ_socket_id(socket);

/*    if (fstat(sockfd, &buf) < 0) marche pas!!!!!
    	{
	    puts("\nSocketIsOpen: socket not open") ;
	    return v;
    	} */

    v.intValue = 1;

    return v;

}

/* implementation of native waba/io/Socket.setReadTimeout() */
Var SocketSetReadTimeout(Var stack[]) {
    Var v; 
    
    /* return nothing */
    v.obj = 0;
    return v;

}

/* implementation of function for reading/writing in a socket */
Var SocketReadWriteBytes( Var stack[], int isRead ) {
    Var v; 

    WObject socket, byteArray;
    int sock;
    int32 start, count, countSoFar, n;
//    uchar * bytes;
    byte *bytes;

    v.intValue = -1;

    socket = stack[0].obj;
    byteArray = stack[1].obj;
    start = stack[2].intValue;
    count = stack[3].intValue;
    sock = WOBJ_socket_id(socket);

    if (sock == -1)
	return v;

    if (arrayRangeCheck(byteArray, start, count) == 0)
	return v;

    bytes = (uchar *)WOBJ_arrayStart(byteArray);
    bytes = &bytes[start];
    countSoFar = 0;

    while (countSoFar < count)
    {
	int chunkSize;

	chunkSize = count - countSoFar;

	if (chunkSize > 0x7000) // one #define would be better
	    chunkSize = 0x7000;

	if (isRead)
		{
		sock_type *tcp_s;
		int r;

///////////////
		tcp_s = &(Socket[sock]->s);
		while ( 1 )
			{
		    if ( !tcp_tick(tcp_s)) 
		    	{ break ; }

			if (sock_dataready( tcp_s ) )
				{
//				n = sock_fastread( tcp_s, bytes, sizeof(bytes));
				n = sock_fastread( tcp_s, bytes, chunkSize);
				bytes[r]='\0';
				sock_close(tcp_s);
				break ; 
				}
    		}
//////////////	    
		}
	else
	    n = write_s(sock, (char *) bytes, chunkSize);

	if (n > 0)
		{
	    countSoFar += n;
	    bytes += n;
		}
	else
		{
	    if (countSoFar != 0)
		v.intValue = countSoFar;
	    else if (n < 0)
		v.intValue = 0;
	    else
		v.intValue = -1;
	    return v;
		}
    }
    v.intValue = count;
    return v;    // pas de retour !!!!
}

/* implementation of native waba/io/Socket.readBytes() */
Var SocketRead( Var stack[] ) 
{
Var v ;
	
v = SocketReadWriteBytes( stack, 1 );
return v ;
}

/* implementation of native waba/io/Socket.writeBytes() */
Var SocketWrite(Var stack[]) 
{

Var v ;
	
v = SocketReadWriteBytes( stack, 0 );
return v ;
}


//
// Sound
//
// all functions static

static Var SoundTone(Var stack[])
	{
    int nFrequency, nDuration;
	Var v;

	nFrequency = stack[0].intValue; // freq
	nDuration = stack[1].intValue; // duration
    sound(nFrequency);
    usleep(nDuration*1000);
    nosound();

	v.obj = 0;
	return v;
	}

static Var SoundBeep(Var stack[])
	{
	Var v;

    sound(440);
    usleep(200000);
    nosound();

	v.obj = 0;
	return v;
	}

//
// SoundClip
//

#define SoundClipPlay Return0Func

//
// Convert
//

static Var ConvertFloatToIntBitwise(Var stack[])
	{
	return stack[0];
	}

static Var ConvertIntToFloatBitwise(Var stack[])
	{
	return stack[0];
	}

static Var ConvertStringToInt(Var stack[])
	{
	WObject string, charArray;
	int32 i, isNeg, len, value;
	uint16 *chars;
	Var v;

	// NOTE: We do it all here instead of calling atoi() since it looks
	// like various versions of CE don't support atoi(). It's also faster
	// this way since we don't have to convert to a byte array.
	v.intValue = 0;
	string = stack[0].obj;
	if (string == 0)
		return v;
	charArray = WOBJ_StringCharArrayObj(string);
	if (charArray == 0)
		return v;
	chars = (uint16 *)WOBJ_arrayStart(charArray);
	len = WOBJ_arrayLen(charArray);
	isNeg = 0;
	if (len > 0 && chars[0] == '-')
		isNeg = 1;
	value = 0;
	for (i = isNeg; i < len; i++)
		{
		if (chars[i] < (uint16)'0' || chars[i] > (uint16)'9')
			return v;
		value = (value * 10) + ((int32)chars[i] - (int32)'0');
		}
	if (isNeg)
		value = -(value);
	v.intValue = value;
	return v;
	}

static Var ConvertIntToString(Var stack[])
	{
	Var v;
	char buf[20];

	sprintf(buf, "%d", stack[0].intValue);
	v.obj = createString(buf);
	return v;
	}

static Var ConvertFloatToString(Var stack[])
	{
	Var v;
	char buf[40];

	sprintf(buf, "%f", stack[0].floatValue);
	v.obj = createString(buf);
	return v;
	}

static Var ConvertCharToString(Var stack[])
	{
	Var v;
	char buf[2];

	buf[0] = (char)stack[0].intValue;
	buf[1] = 0;
	v.obj = createString(buf);
	return v;
	}

static Var ConvertBooleanToString(Var stack[])
	{
	Var v;
	char *s;

	if (stack[0].intValue == 0)
		s = "false";
	else
		s = "true";
	v.obj = createString(s);
	return v;
	}

//
// Catalog
//
// var[0] = Class
// var[1] = hook var - DmOpenRef (null if no open db)
// var[2] = hook var - current record pos (or -1 if none)
// var[3] = hook var - VoidHand current record handle
// var[4] = hook var - VoidPtr current record pointer
// var[5] = hook var - current offset in record
// var[6] = hook var - length of current record
// var[7] = hook var - 1 if record has been modified, 0 otherwise
#define WOBJ_CatalogDmRef(o) (objectPtr(o))[1].refValue
#define WOBJ_CatalogCurRecPos(o) (objectPtr(o))[2].intValue
#define WOBJ_CatalogCurRecHandle(o) (objectPtr(o))[3].refValue
#define WOBJ_CatalogCurRecPtr(o) (objectPtr(o))[4].refValue
#define WOBJ_CatalogCurRecOffset(o) (objectPtr(o))[5].intValue
#define WOBJ_CatalogCurRecLen(o) (objectPtr(o))[6].intValue
#define WOBJ_CatalogCurRecModified(o) (objectPtr(o))[7].intValue

#define Catalog_READ_ONLY 1
#define Catalog_WRITE_ONLY 2
#define Catalog_READ_WRITE 3
#define Catalog_CREATE 4

static void _RecClose(WObject cat)
	{
	DmOpenRef dmRef;
	VoidHand recH;
	int32 pos;
	Boolean dirty;

	pos = WOBJ_CatalogCurRecPos(cat);
	if (pos == -1)
		return; // no current record
	recH = WOBJ_CatalogCurRecHandle(cat);
	MemHandleUnlock(recH);
	dmRef = WOBJ_CatalogDmRef(cat);
	if (WOBJ_CatalogCurRecModified(cat) != 0)
		dirty = true;
	else
		dirty = false;
	DmReleaseRecord(dmRef, (UInt16) pos, dirty);
	WOBJ_CatalogCurRecPos(cat) = -1;	
	}

static int32 _CatalogClose(WObject cat)
	{
	DmOpenRef dmRef;

	_RecClose(cat); // release any open records
	dmRef = (DmOpenRef)WOBJ_CatalogDmRef(cat);
	if (dmRef == 0)
		return 0;
	DmCloseDatabase(dmRef);
	WOBJ_CatalogDmRef(cat) = 0;
	return 1;
	}

static DmOpenRef _CatalogOpenDB(char *name, ULong dbCreator, ULong dbType)
	{
	DmSearchStateType state;
	Boolean first;
	UInt cardNo;
	LocalID dbID;
	char dbName[32];
	Err err;

	first = true;
	while (1)
		{
		err = DmGetNextDatabaseByTypeCreator(first, &state,
			dbType, dbCreator, false, &cardNo, &dbID);
		if (err == dmErrCantFind)
			return NULL;
	 	if (DmDatabaseInfo(cardNo, dbID, dbName, NULL,
			NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL) == 0)
			{
			if (!StrCompare(name, dbName))
				return DmOpenDatabase(cardNo, dbID, dmModeReadWrite);
			}
		first = false;
		}
	}

static Var CatalogCreate(Var stack[])
	{
	UtfString s;
	WObject cat, name;
	int32 mode;
	DmOpenRef dmRef;
	ULong dbType, dbCreator;
	Var v;

	v.obj = 0;
	cat = stack[0].obj;
	name = stack[1].obj;
	mode = stack[2].intValue;
	WOBJ_CatalogDmRef(cat) = 0;
	WOBJ_CatalogCurRecPos(cat) = -1;
	s = stringToUtf(name, STU_USE_STATIC | STU_NULL_TERMINATE);
	if (s.len == 0)
		return v;
	dbCreator = appCreatorId;
//	dbType = 'DATA';
	if (s.len > 10 && s.str[s.len - 10] == '.' && s.str[s.len - 5] == '.')
		{
		s.str[s.len - 10] = 0;
		dbCreator = getUInt32((uchar *)&s.str[s.len - 9]);
		dbType = getUInt32((uchar *)&s.str[s.len - 4]);
		}
	dmRef = _CatalogOpenDB(s.str, dbCreator, dbType);
	if (mode == Catalog_CREATE && !dmRef)
		{
		if (DmCreateDatabase(0, s.str, dbCreator, dbType, false) != 0)
			return v;
		dmRef = _CatalogOpenDB(s.str, dbCreator, dbType);
		// set the backup bit on the database
		}
	if (dmRef == 0)
		return v;
	WOBJ_CatalogDmRef(cat) = dmRef;
	return v;
	}

static Var CatalogIsOpen(Var stack[])
	{
	WObject cat;
	Var v;
	DmOpenRef dmRef;

	cat = stack[0].obj;
	dmRef = (DmOpenRef)WOBJ_CatalogDmRef(cat);
	if (dmRef == 0)
		v.intValue = 0;
	else
		v.intValue = 1;
	return v;
	}

static void CatalogDestroy(WObject cat)
	{
	_CatalogClose(cat);
	}

static Var CatalogClose(Var stack[])
	{
	WObject cat;
	Var v;

	cat = stack[0].obj;
	v.intValue = _CatalogClose(cat);
	return v;
	}

static Var CatalogDelete(Var stack[])
	{
	WObject cat;
	DmOpenRef dmRef;
	LocalID localID;
	UInt cardNo;
	Var v;

	v.intValue = 0;
	cat = stack[0].obj;
	dmRef = (DmOpenRef)WOBJ_CatalogDmRef(cat);
	if (dmRef == 0)
		return v;
	DmOpenDatabaseInfo(dmRef, &localID, NULL, NULL, &cardNo, NULL);
	_CatalogClose(cat);
	if (DmDeleteDatabase(cardNo, localID) != 0)
		return v;
	v.intValue = 1;
	return v;
	}

static void _ULongToChars(ULong l, char *buf)
	{
	buf[0] = (char) (l >> 24);
	buf[1] = (char) ((l >> 16) & 0xFF);
	buf[2] = (char) ((l >> 8) & 0xFF);
	buf[3] = (char) (l & 0xFF);
	}

static Var CatalogListCatalogs(Var stack[])
	{
	WObject stringArray, *strings;
	DmSearchStateType state;
	Boolean first;
	UInt cardNo;
	LocalID dbID;
	ULong dbType, dbCreator;
	int n, len;
	char name[32 + 10];
	Var v;

	v.obj = 0;
	n = 0;
	first = true;
	while (DmGetNextDatabaseByTypeCreator(first,
		&state, 0, 0, false, &cardNo, &dbID) == 0)
		{
		first = false;
		n++;
		}
	if (n == 0)
		return v;
	stringArray = createArrayObject(1, n);
	if (pushObject(stringArray) == -1)
		return v;
	n = 0;
	first = true;
	while (DmGetNextDatabaseByTypeCreator(first, &state,
			0, 0, false, &cardNo, &dbID) == 0)
		{
		first = false;
		// we need to recompute the start pointer each iteration
		// in case garbage collection during a string create causes
		// memory to move around
		strings = (WObject *)WOBJ_arrayStart(stringArray);
		name[0] = 0;
	 	DmDatabaseInfo(cardNo, dbID, name, NULL, NULL, NULL, NULL,
			NULL, NULL, NULL, NULL, &dbType, &dbCreator);
		len = xstrlen(name);
		name[len++] = '.';
		_ULongToChars(dbCreator, &name[len]);
		len += 4;
		name[len++] = '.';
		_ULongToChars(dbType, &name[len]);
		len += 4;
		name[len++] = 0;
		strings[n++] = createString(name);
		}
	popObject(); // stringArray
	v.obj = stringArray;
	return v;
	}

static Var CatalogGetRecordSize(Var stack[])
	{
	WObject cat;
	int32 pos;
	Var v;

	cat = stack[0].obj;
	pos = WOBJ_CatalogCurRecPos(cat);
	if (pos == -1)
		{
		v.intValue = -1;
		return v;
		}
	v.intValue = WOBJ_CatalogCurRecLen(cat);
	return v;
	}

static Var CatalogGetRecordCount(Var stack[])
	{
	WObject cat;
	DmOpenRef dmRef;
	Var v;

	cat = stack[0].obj;
	dmRef = (DmOpenRef)WOBJ_CatalogDmRef(cat);
	if (dmRef == 0)
		{
		v.intValue = -1;
		return v;
		}
	v.intValue = DmNumRecords(dmRef);
	return v;
	}

static Var CatalogDeleteRecord(Var stack[])
	{
	WObject cat;
	int32 pos;
	DmOpenRef dmRef;
	Var v;

	v.intValue = 0;
	cat = stack[0].obj;
	pos = WOBJ_CatalogCurRecPos(cat);
	if (pos == -1)
		return v;
	_RecClose(cat);
	dmRef = (DmOpenRef)WOBJ_CatalogDmRef(cat);
	// NOTE: Was DmDeleteRecord() but this didn't match with CE
	// and was too difficult to use in actual programs since it
	// didn't delete the record immediately
	if (DmRemoveRecord(dmRef, (UInt16) pos) != 0)
		return v;
	v.intValue = 1;
	return v;
	}

static Var CatalogResizeRecord(Var stack[])
	{
	WObject cat;
	DmOpenRef dmRef;
	VoidHand recH;
	VoidPtr recPtr;
	int32 pos, size;
	Var v;

	cat = stack[0].obj;
	size = stack[1].intValue;
	v.intValue = 0;
	if (size < 0)
		return v;
	pos = WOBJ_CatalogCurRecPos(cat);
	if (pos == -1)
		return v;
	_RecClose(cat);
	dmRef = (DmOpenRef)WOBJ_CatalogDmRef(cat);
	if (dmRef == 0)
		return v;
	recH = DmResizeRecord(dmRef, (UInt16) pos, (ULong)size);
	if (recH == NULL)
		return v;
	recPtr = MemHandleLock(recH);
	if (recPtr == NULL)
		return v;
	WOBJ_CatalogCurRecPos(cat) = pos;
	WOBJ_CatalogCurRecHandle(cat) = recH;
	WOBJ_CatalogCurRecPtr(cat) = recPtr;
	WOBJ_CatalogCurRecLen(cat) = size;
	WOBJ_CatalogCurRecModified(cat) = true;
	v.intValue = 1;
	return v;
	}

static Var CatalogAddRecord(Var stack[])
	{
	WObject cat;
	DmOpenRef dmRef;
	VoidHand recH;
	VoidPtr recPtr;
	UInt pos;
	int32 size;
	Var v;

	cat = stack[0].obj;
	size = stack[1].intValue;
	v.intValue = -1;
	_RecClose(cat);
	dmRef = (DmOpenRef)WOBJ_CatalogDmRef(cat);
	if (dmRef == 0)
		return v;
	pos = DmNumRecords(dmRef);
	recH = DmNewRecord(dmRef, &pos, size);
	if (!recH)
		return v;
	recPtr = MemHandleLock(recH);
	if (!recPtr)
		{
		DmDeleteRecord(dmRef, pos);
		return v;
		}
	WOBJ_CatalogCurRecPos(cat) = pos;
	WOBJ_CatalogCurRecHandle(cat) = recH;
	WOBJ_CatalogCurRecPtr(cat) = recPtr;
	WOBJ_CatalogCurRecOffset(cat) = 0;
	WOBJ_CatalogCurRecLen(cat) = size;
	WOBJ_CatalogCurRecModified(cat) = true;
	v.intValue = pos;
	return v;
	}

static Var CatalogSetRecordPos(Var stack[])
	{
	WObject cat;
	int32 pos, count;
	DmOpenRef dmRef;
	VoidHand recH;
	VoidPtr recPtr;
	Var v;

	v.intValue = 0;
	cat = stack[0].obj;
	pos = stack[1].intValue;
	_RecClose(cat);
	if (pos < 0)
		return v;
	dmRef = (DmOpenRef)WOBJ_CatalogDmRef(cat);
	if (dmRef == 0)
		return v;
	count = (int32)DmNumRecords(dmRef);
	if (pos >= count)
		return v;
	recH = DmGetRecord(dmRef, (UInt16) pos);
	if (!recH)
		return v;
	recPtr = MemHandleLock(recH);
	if (!recPtr)
		{
		DmReleaseRecord(dmRef, (UInt16) pos, false);
		return v;
		}
	WOBJ_CatalogCurRecPos(cat) = pos;
	WOBJ_CatalogCurRecHandle(cat) = recH;
	WOBJ_CatalogCurRecPtr(cat) = recPtr;
	WOBJ_CatalogCurRecOffset(cat) = 0;
	WOBJ_CatalogCurRecLen(cat) = MemHandleSize(recH);
	WOBJ_CatalogCurRecModified(cat) = 0;
	v.intValue = 1;
	return v;
	}

static Var CatalogReadWriteBytes(Var stack[], int isRead)
	{
	WObject cat, byteArray;
	int32 start, count, pos, recOffset;
	uchar *bytes, *recBytes;
	Var v;

	v.intValue = -1;
	cat = stack[0].obj;
	byteArray = stack[1].obj;
	start = stack[2].intValue;
	count = stack[3].intValue;
	pos = WOBJ_CatalogCurRecPos(cat);
	if (pos == -1)
		return v; // no current record
	if (arrayRangeCheck(byteArray, start, count) == 0)
		return v; // array null or range invalid
	recBytes = WOBJ_CatalogCurRecPtr(cat);
	recOffset = WOBJ_CatalogCurRecOffset(cat);
	if (recOffset + count > WOBJ_CatalogCurRecLen(cat))
		return v; // past end of record
	bytes = (uchar *)WOBJ_arrayStart(byteArray);
	if (isRead)
		xmemmove(&bytes[start], &recBytes[recOffset], count);
	else
		{
		if (DmWrite(recBytes, recOffset, &bytes[start], count) != 0)
			return v;
		WOBJ_CatalogCurRecModified(cat) = 1;
		}
	WOBJ_CatalogCurRecOffset(cat) = recOffset + count;
	v.intValue = count;
	return v;
	}

static Var CatalogRead(Var stack[])
	{
	return CatalogReadWriteBytes(stack, 1);
	}

static Var CatalogWrite(Var stack[])
	{
	return CatalogReadWriteBytes(stack, 0);
	}

static Var CatalogSkipBytes(Var stack[])
	{
	WObject cat;
	int32 count, pos, offset;
	Var v;

	v.intValue = -1;
	cat = stack[0].obj;
	count = stack[1].obj;
	pos = WOBJ_CatalogCurRecPos(cat);
	if (count < 0 || pos == -1)
		return v;
	offset = WOBJ_CatalogCurRecOffset(cat);
	if (offset + count > WOBJ_CatalogCurRecLen(cat))
		return v;
	WOBJ_CatalogCurRecOffset(cat) += count;
	v.intValue = count;
	return v;
	}

//
// Time
//
// var[0] = Class
// var[1] = int year
// var[2] = int month
// var[3] = int day
// var[4] = int hour
// var[5] = int minute
// var[6] = int second
// var[7] = int millis
//

#define WOBJ_TimeYear(o) (objectPtr(o))[1].intValue
#define WOBJ_TimeMonth(o) (objectPtr(o))[2].intValue
#define WOBJ_TimeDay(o) (objectPtr(o))[3].intValue
#define WOBJ_TimeHour(o) (objectPtr(o))[4].intValue
#define WOBJ_TimeMinute(o) (objectPtr(o))[5].intValue
#define WOBJ_TimeSecond(o) (objectPtr(o))[6].intValue
#define WOBJ_TimeMillis(o) (objectPtr(o))[7].intValue

static Var TimeCreate(Var stack[])
	{
	Var v;
	WObject aTime;
    struct tm* pTm;
    time_t now;

	aTime = stack[0].obj;
    time(&now);
    pTm = localtime(&now);
	WOBJ_TimeYear(aTime) = pTm->tm_year + 1900;
	WOBJ_TimeMonth(aTime) = pTm->tm_mon + 1;
	WOBJ_TimeDay(aTime) = pTm->tm_mday;
	WOBJ_TimeHour(aTime) = pTm->tm_hour;
	WOBJ_TimeMinute(aTime) = pTm->tm_min;
	WOBJ_TimeSecond(aTime) = pTm->tm_sec;
	WOBJ_TimeMillis(aTime) = getTimeStamp() % 1000;
	v.obj = 0;
	return v;
	}

//
// SerialPort
//

static Var SerialPortCreate(Var stack[])
	{
	Var v;

	v.obj = 0;
	return v;
	}

static void SerialPortDestroy(WObject port)
	{
	}

static Var SerialPortIsOpen(Var stack[])
	{
	Var v;

	v.obj = 0;
	return v;
	}

static Var SerialPortSetReadTimeout(Var stack[])
	{
	Var v;

	v.obj = 0;
	return v;
	}

static Var SerialPortReadCheck(Var stack[])
	{
	Var v;

	v.obj = 0;
	return v;
	}

static Var SerialPortSetFlowControl(Var stack[])
	{
	Var v;

	v.obj = 0;
	return v;
	}

static Var SerialPortReadWriteBytes(Var stack[], int isRead)
	{
	Var v;

	v.obj = 0;
	return v;
	}

static Var SerialPortRead(Var stack[])
	{
	return SerialPortReadWriteBytes(stack, 1);
	}

static Var SerialPortWrite(Var stack[])
	{
	return SerialPortReadWriteBytes(stack, 0);
	}

static Var SerialPortClose(Var stack[])
	{
	Var v;

	v.obj = 0;
	return v;
	}

//
// Vm
//

static Var VmIsColor(Var stack[])
{
	Var v;

	v.intValue = getVMIsColor();
	return v;
}

static Var VmGetTimeStamp(Var stack[])
{
	Var v;

	v.intValue = getTimeStamp();
	return v;
}

static Var VmExec(Var stack[])
	{
	WObject pathString, argsString;
	UtfString path, args;
	int32 doWait;
	int status;
	Var v;
	char argsBuf[256];

	v.intValue = -1;
	pathString = stack[0].obj;
	argsString = stack[1].obj;
	doWait = stack[3].intValue;
	// NOTE: can't use static here since we call stringToUtf on both
	// path and args
	path = stringToUtf(pathString, STU_NULL_TERMINATE);
	if (path.len == 0)
		return v;
	args = stringToUtf(argsString, STU_NULL_TERMINATE | STU_USE_STATIC);

    /*
    @@
	sInfo.cb = sizeof(sInfo);
	// NOTE: CreateProcess() is kind of screwey and the arguments parameter
	// is supposed to include the program name. But if the program name contains
	// a space, it gets confused. So, we use a program name of x here to keep it
	// from getting confused if there is a space in the program name.
	if (args.len + 3 > 256)
		return v;
	argsBuf[0] = 'x';
	argsBuf[1] = ' ';
	xstrncpy(&argsBuf[2], args.str, args.len);
	argsBuf[args.len + 2] = 0;

	if (doWait)
	{
		WaitForSingleObject(pInfo.hProcess, INFINITE);
		v.intValue = GetExitCodeProcess(pInfo.hProcess, &exitCode);
	}
	else
		v.intValue = 0;
    */
	return v;
}

static Var VmSleep(Var stack[])
{
	Var v;

	int32 millis;
	millis = stack[0].intValue;

#ifdef _WIN32
	Sleep(millis);
#else
    usleep(millis*1000);
#endif

	v.obj = 0;
	return v;
}

static Var VmGetPlatform(Var stack[])
{
	Var v;

	v.obj = createString("DOS/GRX");
	return v;
}

static Var VmSetDeviceAutoOff(Var stack[])
{
	Var v;

	v.obj = 0;
	return v;
}

static Var VmGetUserName(Var stack[])
{
	Var v;

    char* psz = getenv("USER");
    if (!psz) psz=getenv("USERNAME");
    if (!psz) psz=getenv("USERID");

    if (!psz)
        v.obj = createString("USER");
    else
        v.obj = createString(psz);
	return v;
}

//
// Console
//

static Var ConsolePrint(Var stack[])
{
	Var v;
	WObject obj;
    UtfString utfstr;

    obj = stack[0].obj;
    utfstr = stringToUtf(obj,STU_NULL_TERMINATE);
#ifdef DEBUGVERBOSE
    // @@@
    fprintf(pfilLog, "%s",utfstr.str);
    fflush(pfilLog);
#endif

	v.obj = 0;
	return v;
}

static Var ConsoleGetLine(Var stack[])
{
	Var v;
    char sz[400];
	v.obj = createString(gets(sz));
	return v;
}
