/* ScummVM - Scumm Interpreter
 * Copyright (C) 2001  Ludvig Strigeus
 * Copyright (C) 2001-2004 The ScummVM project
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header$
 *
 */


#include "stdafx.h"

#include "common/config-manager.h"

#include "scumm/actor.h"
#include "scumm/charset.h"
#include "scumm/intern.h"
#include "scumm/object.h"
#include "scumm/resource.h"
#include "scumm/resource_v7he.h"
#include "scumm/scumm.h"
#include "scumm/sound.h"
#include "scumm/verbs.h"

#include "sound/mididrv.h"
#include "sound/mixer.h"

namespace Scumm {

#define OPCODE(x)	_OPCODE(ScummEngine_v72he, x)

void ScummEngine_v72he::setupOpcodes() {
	static const OpcodeEntryV72he opcodes[256] = {
		/* 00 */
		OPCODE(o6_pushByte),
		OPCODE(o6_pushWord),
		OPCODE(o72_pushDWord),
		OPCODE(o6_pushWordVar),
		/* 04 */
		OPCODE(o72_addMessageToStack),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_wordArrayRead),
		/* 08 */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_wordArrayIndexedRead),
		/* 0C */
		OPCODE(o6_dup),
		OPCODE(o6_not),
		OPCODE(o6_eq),
		OPCODE(o6_neq),
		/* 10 */
		OPCODE(o6_gt),
		OPCODE(o6_lt),
		OPCODE(o6_le),
		OPCODE(o6_ge),
		/* 14 */
		OPCODE(o6_add),
		OPCODE(o6_sub),
		OPCODE(o6_mul),
		OPCODE(o6_div),
		/* 18 */
		OPCODE(o6_land),
		OPCODE(o6_lor),
		OPCODE(o6_pop),
		OPCODE(o72_isAnyOf),
		/* 1C */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		/* 20 */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		/* 24 */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		/* 28 */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		/* 2C */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		/* 30 */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		/* 34 */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		/* 38 */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		/* 3C */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		/* 40 */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_writeWordVar),
		/* 44 */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_wordArrayWrite),
		/* 48 */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_wordArrayIndexedWrite),
		/* 4C */
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_wordVarInc),
		/* 50 */
		OPCODE(o72_resetCutscene),
		OPCODE(o6_invalid),
		OPCODE(o72_findObjectWithClassOf),
		OPCODE(o6_wordArrayInc),
		/* 54 */
		OPCODE(o72_getObjectImageX),
		OPCODE(o72_getObjectImageY),
		OPCODE(o72_captureWizImage),
		OPCODE(o6_wordVarDec),
		/* 58 */
		OPCODE(o72_getTimer),
		OPCODE(o72_setTimer),
		OPCODE(o72_unknown5A),
		OPCODE(o6_wordArrayDec),
		/* 5C */
		OPCODE(o6_if),
		OPCODE(o6_ifNot),
		OPCODE(o72_startScript),
		OPCODE(o6_startScriptQuick),
		/* 60 */
		OPCODE(o72_startObject),
		OPCODE(o72_drawObject),
		OPCODE(o72_printWizImage),
		OPCODE(o72_getArrayDimSize),
		/* 64 */
		OPCODE(o72_getNumFreeArrays),
		OPCODE(o6_stopObjectCode),
		OPCODE(o6_stopObjectCode),
		OPCODE(o6_endCutscene),
		/* 68 */
		OPCODE(o6_cutscene),
		OPCODE(o6_stopMusic),
		OPCODE(o6_freezeUnfreeze),
		OPCODE(o6_cursorCommand),
		/* 6C */
		OPCODE(o6_breakHere),
		OPCODE(o6_ifClassOfIs),
		OPCODE(o6_setClass),
		OPCODE(o6_getState),
		/* 70 */
		OPCODE(o60_setState),
		OPCODE(o6_setOwner),
		OPCODE(o6_getOwner),
		OPCODE(o6_jump),
		/* 74 */
		OPCODE(o70_startSound),
		OPCODE(o6_stopSound),
		OPCODE(o6_startMusic),
		OPCODE(o6_stopObjectScript),
		/* 78 */
		OPCODE(o6_panCameraTo),
		OPCODE(o6_actorFollowCamera),
		OPCODE(o6_setCameraAt),
		OPCODE(o6_loadRoom),
		/* 7C */
		OPCODE(o6_stopScript),
		OPCODE(o6_walkActorToObj),
		OPCODE(o6_walkActorTo),
		OPCODE(o6_putActorAtXY),
		/* 80 */
		OPCODE(o6_putActorAtObject),
		OPCODE(o6_faceActor),
		OPCODE(o6_animateActor),
		OPCODE(o6_doSentence),
		/* 84 */
		OPCODE(o70_pickupObject),
		OPCODE(o6_loadRoomWithEgo),
		OPCODE(o6_invalid),
		OPCODE(o6_getRandomNumber),
		/* 88 */
		OPCODE(o6_getRandomNumberRange),
		OPCODE(o6_invalid),
		OPCODE(o6_getActorMoving),
		OPCODE(o6_isScriptRunning),
		/* 8C */
		OPCODE(o70_getActorRoom),
		OPCODE(o6_getObjectX),
		OPCODE(o6_getObjectY),
		OPCODE(o6_getObjectOldDir),
		/* 90 */
		OPCODE(o6_getActorWalkBox),
		OPCODE(o6_getActorCostume),
		OPCODE(o6_findInventory),
		OPCODE(o6_getInventoryCount),
		/* 94 */
		OPCODE(o6_getVerbFromXY),
		OPCODE(o6_beginOverride),
		OPCODE(o6_endOverride),
		OPCODE(o6_setObjectName),
		/* 98 */
		OPCODE(o6_isSoundRunning),
		OPCODE(o6_setBoxFlags),
		OPCODE(o6_invalid),
		OPCODE(o70_resourceRoutines),
		/* 9C */
		OPCODE(o72_roomOps),
		OPCODE(o72_actorOps),
		OPCODE(o72_verbOps),
		OPCODE(o6_getActorFromXY),
		/* A0 */
		OPCODE(o70_findObject),
		OPCODE(o6_pseudoRoom),
		OPCODE(o6_getActorElevation),
		OPCODE(o6_getVerbEntrypoint),
		/* A4 */
		OPCODE(o72_arrayOps),
		OPCODE(o6_saveRestoreVerbs),
		OPCODE(o6_drawBox),
		OPCODE(o6_pop),
		/* A8 */
		OPCODE(o6_getActorWidth),
		OPCODE(o60_wait),
		OPCODE(o6_getActorScaleX),
		OPCODE(o6_getActorAnimCounter1),
		/* AC */
		OPCODE(o6_invalid),
		OPCODE(o6_isAnyOf),
		OPCODE(o70_quitPauseRestart),
		OPCODE(o6_isActorInBox),
		/* B0 */
		OPCODE(o6_delay),
		OPCODE(o6_delaySeconds),
		OPCODE(o6_delayMinutes),
		OPCODE(o6_stopSentence),
		/* B4 */
		OPCODE(o6_printLine),
		OPCODE(o6_printCursor),
		OPCODE(o6_printDebug),
		OPCODE(o6_printSystem),
		/* B8 */
		OPCODE(o6_printActor),
		OPCODE(o6_printEgo),
		OPCODE(o6_talkActor),
		OPCODE(o6_talkEgo),
		/* BC */
		OPCODE(o72_dimArray),
		OPCODE(o6_stopObjectCode),
		OPCODE(o6_startObjectQuick),
		OPCODE(o6_startScriptQuick2),
		/* C0 */
		OPCODE(o72_dim2dimArray),
		OPCODE(o72_traceStatus),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		/* C4 */
		OPCODE(o6_abs),
		OPCODE(o6_distObjectObject),
		OPCODE(o6_distObjectPt),
		OPCODE(o6_distPtPt),
		/* C8 */
		OPCODE(o72_kernelGetFunctions),
		OPCODE(o70_kernelSetFunctions),
		OPCODE(o6_delayFrames),
		OPCODE(o6_pickOneOf),
		/* CC */
		OPCODE(o6_pickOneOfDefault),
		OPCODE(o6_stampObject),
		OPCODE(o72_drawWizImage),
		OPCODE(o72_unknownCF),
		/* D0 */
		OPCODE(o6_getDateTime),
		OPCODE(o6_stopTalking),
		OPCODE(o6_getAnimateVariable),
		OPCODE(o6_invalid),
		/* D4 */
		OPCODE(o6_shuffle),
		OPCODE(o72_jumpToScript),
		OPCODE(o6_band),
		OPCODE(o6_bor),
		/* D8 */
		OPCODE(o6_isRoomScriptRunning),
		OPCODE(o60_closeFile),
		OPCODE(o72_openFile),
		OPCODE(o72_readFile),
		/* DC */
		OPCODE(o72_writeFile),
		OPCODE(o72_findAllObjects),
		OPCODE(o72_deleteFile),
		OPCODE(o60_rename),
		/* E0 */
		OPCODE(o60_soundOps),
		OPCODE(o72_getPixel),
		OPCODE(o60_localizeArrayToScript),
		OPCODE(o72_pickVarRandom),
		/* E4 */
		OPCODE(o6_setBoxSet),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		/* E8 */
		OPCODE(o6_invalid),
		OPCODE(o60_seekFilePos),
		OPCODE(o72_redimArray),
		OPCODE(o60_readFilePos),
		/* EC */
		OPCODE(o72_copyString),
		OPCODE(o70_getStringWidth),
		OPCODE(o70_getStringLen),
		OPCODE(o70_appendString),
		/* F0 */
		OPCODE(o72_concatString),
		OPCODE(o70_compareString),
		OPCODE(o72_checkGlobQueue),
		OPCODE(o72_readINI),
		/* F4 */
		OPCODE(o72_writeINI),
		OPCODE(o70_getStringLenForWidth),
		OPCODE(o70_getCharIndexInString),
		OPCODE(o6_invalid),
		/* F8 */
		OPCODE(o72_getResourceSize),
		OPCODE(o72_setFilePath),
		OPCODE(o72_setWindowCaption),
		OPCODE(o70_polygonOps),
		/* FC */
		OPCODE(o70_polygonHit),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
		OPCODE(o6_invalid),
	};

	_opcodesV72he = opcodes;
}

void ScummEngine_v72he::executeOpcode(byte i) {
	OpcodeProcV72he op = _opcodesV72he[i].proc;
	(this->*op) ();
}

const char *ScummEngine_v72he::getOpcodeDesc(byte i) {
	return _opcodesV72he[i].desc;
}

static const int arrayDataSizes[] = { 0, 1, 4, 8, 8, 16, 32 };

ScummEngine_v72he::ArrayHeader *ScummEngine_v72he::defineArray(int array, int type, int dim2start, int dim2end,
											int dim1start, int dim1end) {
	int id;
	int size;
	ArrayHeader *ah;
	
	assert(dim2start >= 0 && dim2start <= dim2end);
	assert(dim1start >= 0 && dim1start <= dim1end);
	assert(0 <= type && type <= 6);

	
	if (type == kBitArray || type == kNibbleArray)
		type = kByteArray;

	nukeArray(array);

	id = findFreeArrayId();

	debug(5,"defineArray (array %d, dim2start %d, dim2end %d dim1start %d dim1end %d", id, dim2start, dim2end, dim1start, dim1end);

	if (array & 0x80000000) {
		error("Can't define bit variable as array pointer");
	}

	size = arrayDataSizes[type];

	writeVar(array, id);

	size *= dim2end - dim2start + 1;
	size *= dim1end - dim1start + 1;
	size >>= 3;

	ah = (ArrayHeader *)createResource(rtString, id, size + sizeof(ArrayHeader));

	ah->type = TO_LE_32(type);
	ah->dim1start = TO_LE_32(dim1start);
	ah->dim1end = TO_LE_32(dim1end);
	ah->dim2start = TO_LE_32(dim2start);
	ah->dim2end = TO_LE_32(dim2end);

	return ah;
}

int ScummEngine_v72he::readArray(int array, int idx2, int idx1) {
	debug(5, "readArray (array %d, idx2 %d, idx1 %d)", readVar(array), idx2, idx1);

	if (readVar(array) == 0)
		error("readArray: Reference to zeroed array pointer");

	ArrayHeader *ah = (ArrayHeader *)getResourceAddress(rtString, readVar(array));

	if (ah == NULL || ah->data == NULL)
		error("readArray: invalid array %d (%d)", array, readVar(array));

	if (idx2 < (int)FROM_LE_32(ah->dim2start) || idx2 > (int)FROM_LE_32(ah->dim2end) || 
		idx1 < (int)FROM_LE_32(ah->dim1start) || idx1 > (int)FROM_LE_32(ah->dim1end)) {
		error("readArray: array %d out of bounds: [%d, %d] exceeds [%d..%d, %d..%d]",
			  array, idx1, idx2, FROM_LE_32(ah->dim1start), FROM_LE_32(ah->dim1end),
			  FROM_LE_32(ah->dim2start), FROM_LE_32(ah->dim2end));
	}

	const int offset = (FROM_LE_32(ah->dim1end) - FROM_LE_32(ah->dim1start) + 1) *
		(idx2 - FROM_LE_32(ah->dim2start)) - FROM_LE_32(ah->dim1start) + idx1;

	switch (FROM_LE_32(ah->type)) {
	case kByteArray:
	case kStringArray:
		return ah->data[offset];

	case kIntArray:
		return (int16)READ_LE_UINT16(ah->data + offset * 2);

	case kDwordArray:
		return (int32)READ_LE_UINT32(ah->data + offset * 4);
	}

	return 0;
}

void ScummEngine_v72he::writeArray(int array, int idx2, int idx1, int value) {
	debug(5, "writeArray (array %d, idx2 %d, idx1 %d, value %d)", readVar(array), idx2, idx1, value);

	if (readVar(array) == 0)
		error("writeArray: Reference to zeroed array pointer");

	ArrayHeader *ah = (ArrayHeader *)getResourceAddress(rtString, readVar(array));

	if (!ah)
		error("writeArray: Invalid array (%d) reference", readVar(array));

	if (idx2 < (int)FROM_LE_32(ah->dim2start) || idx2 > (int)FROM_LE_32(ah->dim2end) || 
		idx1 < (int)FROM_LE_32(ah->dim1start) || idx1 > (int)FROM_LE_32(ah->dim1end)) {
		error("writeArray: array %d out of bounds: [%d, %d] exceeds [%d..%d, %d..%d]",
			  array, idx1, idx2, FROM_LE_32(ah->dim1start), FROM_LE_32(ah->dim1end),
			  FROM_LE_32(ah->dim2start), FROM_LE_32(ah->dim2end));
	}

	const int offset = (FROM_LE_32(ah->dim1end) - FROM_LE_32(ah->dim1start) + 1) *
		(idx2 - FROM_LE_32(ah->dim2start)) - FROM_LE_32(ah->dim1start) + idx1;

	switch (FROM_LE_32(ah->type)) {
	case kByteArray:
	case kStringArray:
		ah->data[offset] = value;
		break;

	case kIntArray:
		WRITE_LE_UINT16(ah->data + offset * 2, value);
		break;

	case kDwordArray:
		WRITE_LE_UINT32(ah->data + offset * 4, value);
		break;
	}
}

void ScummEngine_v72he::readArrayFromIndexFile() {
	int num;
	int a, b, c;

	while ((num = _fileHandle.readUint16LE()) != 0) {
		a = _fileHandle.readUint16LE();
		b = _fileHandle.readUint16LE();
		c = _fileHandle.readUint16LE();

		if (c == 1)
			defineArray(num, kBitArray, 0, a, 0, b);
		else
			defineArray(num, kDwordArray, 0, a, 0, b);
	}
}

void ScummEngine_v72he::copyScriptString(byte *dst) {
	int i = 0;
	byte b;

	int array = pop();
	if (array == -1) {
		int len = resStrLen(_stringBuffer) + 1;
		while (len--)
			*dst++ = _stringBuffer[i++];
	} else {
		writeVar(0, array);
		while ((b = readArray(0, 0, i)) != 0) {
			*dst++ = b;
			i++;
		}
	}
	*dst = 0;
}

void ScummEngine_v72he::decodeScriptString(byte *dst, bool scriptString) {
	int args[31];
	int num = 0, val;
	int len, id;
	byte chr, string[256];
	memset(string, 0, sizeof(string));

	val = getStackList(args, ARRAYSIZE(args));
	args[val] = id = pop();

	if (scriptString) {
		addMessageToStack(_scriptPointer, string, sizeof(string));
		len = resStrLen(_scriptPointer);
		_scriptPointer += len + 1;
	} else {
		copyScriptString(string);
		len = resStrLen(string) + 1;
	}

	while (len--) {
		chr = string[num++];
		if (chr == '%') {
			chr = string[num++];
			if (chr == 'd') {
				dst += snprintf((char *)dst, 10, "%d", args[val--]);
				continue;
			} else if (chr == 's') {
				dst += addStringToStack(dst, 512, args[val--]);
				continue;
			}
		}
		*dst++ = chr;
	}
	*dst = 0;
}

const byte *ScummEngine_v72he::findWrappedBlock(uint32 tag, const byte *ptr, int state, bool errorFlag) {
	if (READ_UINT32(ptr) == MKID('MULT')) {
		const byte *offs, *wrap;
		uint32 size;

		wrap = findResource(MKID('WRAP'), ptr);
		if (wrap == NULL)
			return NULL;

		offs = findResourceData(MKID('OFFS'), wrap);
		if (offs == NULL)
			return NULL;

		size = getResourceDataSize(offs) / 4;
		if ((uint32)state >= (uint32)size)
			return NULL;

		offs += READ_LE_UINT32(offs + state * sizeof(uint32));
		offs = findResourceData(tag, offs - 8);
		if (offs)
			return offs;

		offs = findResourceData(MKID('DEFA'), ptr);
		if (offs == NULL)
			return NULL;

		return findResourceData(tag, offs - 8);
	} else {
		return findResourceData(tag, ptr);
	}
}

void ScummEngine_v72he::o72_pushDWord() {
	int a;
	if (*_lastCodePtr + sizeof(MemBlkHeader) != _scriptOrgPointer) {
		uint32 oldoffs = _scriptPointer - _scriptOrgPointer;
		getScriptBaseAddress();
		_scriptPointer = _scriptOrgPointer + oldoffs;
	}
	a = READ_LE_UINT32(_scriptPointer);
	_scriptPointer += 4;
	push(a);
}

void ScummEngine_v72he::o72_addMessageToStack() {
	_stringLength = resStrLen(_scriptPointer) + 1;
	addMessageToStack(_scriptPointer, _stringBuffer, _stringLength);

	// Filter out pointless trace messages, which often flood
	if (strcmp((char *)_stringBuffer, "no trace") && strcmp((char *)_stringBuffer, "trace on"))
		debug(0,"o72_addMessageToStack(\"%s\")", _scriptPointer);

	_scriptPointer += _stringLength;
}

void ScummEngine_v72he::o72_isAnyOf() {
	int args[128], i = 0;
	int num = getStackList(args, ARRAYSIZE(args));
	int value = pop();

	while (i < num) {
		if (args[i] == value) {
			push(1);
			return;
		}
		i++;
	}

	push(0);
}

void ScummEngine_v72he::o72_resetCutscene() {
	int idx;

	idx = vm.cutSceneStackPointer;
	vm.cutSceneStackPointer = 0;
	vm.cutScenePtr[idx] = 0;
	vm.cutSceneScript[idx] = 0;

	VAR(VAR_OVERRIDE) = 0;
}

void ScummEngine_v72he::o72_findObjectWithClassOf() {
	int args[16], num;

	num = getStackList(args, ARRAYSIZE(args));
	int y = pop();
	int x = pop();
	int r = findObject(x, y, num, args);
	push(r);
}

void ScummEngine_v72he::o72_getObjectImageX() {
	int object = pop();
	int objnum = getObjectIndex(object);

	if (objnum == -1) {
		push(0);
		return;
	}

	push(_objs[objnum].x_pos);
}

void ScummEngine_v72he::o72_getObjectImageY() {
	int object = pop();
	int objnum = getObjectIndex(object);

	if (objnum == -1) {
		push(0);
		return;
	}

	push(_objs[objnum].y_pos);
}

struct wizPackCtx {
	uint32 len;
	uint8 saveCode;
	uint8 saveBuf[0x100];
};

static void wizPackType1Helper1(uint8 *&dst, int len, byte newColor, byte prevColor, wizPackCtx *ctx) {
	assert(len > 0);
	if (newColor == prevColor) {
		do {
			int blockLen = MIN(len, 0x7F);
			len -= blockLen;
			if (dst) {
				*dst++ = (blockLen * 2) | 1;
			}
			++ctx->len;
		} while (len > 0);
	} else {
		do {
			int blockLen = MIN(len, 0x40);
			len -= blockLen;
			if (dst) {
				*dst++ = ((blockLen - 1) * 4) | 2;
			}
			++ctx->len;
			if (dst) {
				*dst++ = newColor;
			}
			++ctx->len;
		} while (len > 0);
	}
}

static void wizPackType1Helper2(uint8 *&dst, int len, wizPackCtx *ctx) {
	assert(len > 0);
	const uint8 *src = ctx->saveBuf;
	do {
		int blockLen = MIN(len, 0x40);
		len -= blockLen;
		if (dst) {
			*dst++ = (blockLen - 1) * 4;
		}
		++ctx->len;
		while (blockLen--) {
			if (dst) {
				*dst++ = *src++;
			}
			++ctx->len;
		}
	} while (len > 0);
}

static int wizPackType1(uint8 *dst, const uint8 *src, int srcPitch, const Common::Rect& rCapt, uint8 tColor) {
	debug(1, "wizPackType1(%d, [%d,%d,%d,%d])", tColor, rCapt.left, rCapt.top, rCapt.right, rCapt.bottom);
	wizPackCtx ctx;
	memset(&ctx, 0, sizeof(ctx));
	
	src += rCapt.top * srcPitch + rCapt.left;
	int w = rCapt.width();
	int h = rCapt.height();
	
	uint8 *nextDstPtr, *curDstPtr;
	uint8 curColor, prevColor;
	int saveBufPos;
	
	nextDstPtr = curDstPtr = 0;
	
	int dataSize = 0;
	while (h--) {
		if (dst) {
			curDstPtr = dst;
			nextDstPtr = dst;
			dst += 2;
		}
		dataSize += 2;
		int numBytes = 0;
		
		int i, code;
		for (i = 0; i < w; ++i) {
			if (src[i] != tColor)
				break;
		}
		if (i != w) {
			curDstPtr = dst;
			ctx.len = 0;
			prevColor = ctx.saveBuf[0] = *src;
			const uint8 *curSrcPtr = src + 1;
			saveBufPos = 1;
			code = (tColor - ctx.saveBuf[0] == 0) ? 1 : 0;
			int curw = w;
			while (curw--) {
				ctx.saveBuf[saveBufPos] = curColor = *curSrcPtr++;
				++saveBufPos;
				if (code == 0) {
					if (curColor == tColor) {
						--saveBufPos;
						wizPackType1Helper2(curDstPtr, saveBufPos, &ctx);
						code = saveBufPos = 1;
						ctx.saveBuf[0] = curColor;
						numBytes = 0;
						prevColor = curColor;
						continue;
					}
					if (saveBufPos > 0x80) {
						--saveBufPos;
						wizPackType1Helper2(curDstPtr, saveBufPos, &ctx);
						saveBufPos = 1;
						ctx.saveBuf[0] = curColor;
						numBytes = 0;
						prevColor = curColor;
						continue;
					}
					if (prevColor != curColor) {
						numBytes = saveBufPos - 1;
						prevColor = curColor;
						continue;
					}
					code = 1;
					if (numBytes != 0) {
						if (saveBufPos - numBytes < 3) {
							code = 0;
						} else {
							wizPackType1Helper2(curDstPtr, numBytes, &ctx);
						}
					}
				}
				if (prevColor != curColor || saveBufPos - numBytes > 0x80) {
					saveBufPos -= numBytes;
					--saveBufPos;
					wizPackType1Helper1(curDstPtr, saveBufPos, prevColor, tColor, &ctx);
					saveBufPos = 1;
					numBytes = 0;
					ctx.saveBuf[0] = curColor;
					code = (tColor - ctx.saveBuf[0] == 0) ? 1 : 0;
				}
				prevColor = curColor;
			}
			if (code == 0) {
				wizPackType1Helper2(curDstPtr, saveBufPos, &ctx);
			} else {
				saveBufPos -= numBytes;
				wizPackType1Helper1(curDstPtr, saveBufPos, prevColor, tColor, &ctx);
			}
			dataSize += ctx.len;
			src += srcPitch;
			if (dst) {
				dst += ctx.len;
				*(uint16 *)nextDstPtr = TO_LE_16(ctx.len);
			}
		}
	}
	return dataSize;
}

static int wizPackType0(uint8 *dst, const uint8 *src, int srcPitch, const Common::Rect& rCapt, uint8 tColor) {
	int w = rCapt.width();
	int h = rCapt.height();
	int size = w * h;
	if (dst) {
		src += rCapt.top * srcPitch + rCapt.left;
		while (h--) {
			memcpy(dst, src, w);
			dst += w;
			src += srcPitch;
		}
	}
	return size;
}

void ScummEngine_v72he::captureWizImage(int resType, int resNum, const Common::Rect& r, bool frontBuffer, int compType) {
	debug(1, "ScummEngine_v72he::captureWizImage(%d, %d, %d, [%d,%d,%d,%d])", resType, resNum, compType, r.left, r.top, r.right, r.bottom);
	uint8 *src = NULL;
	VirtScreen *pvs = &virtscr[kMainVirtScreen];
	if (frontBuffer) {
		src = pvs->getPixels(0, 0);
	} else {
		src = pvs->getBackPixels(0, 0);
	}
	Common::Rect rCapt(0, 0, pvs->w, pvs->h);
	if (rCapt.intersects(r)) {
		rCapt.clip(r);
		const uint8 *palPtr = _currentPalette;

		int w = rCapt.width();
		int h = rCapt.height();
		int tColor = (VAR_WIZ_TCOLOR != 0xFF) ? VAR(VAR_WIZ_TCOLOR) : 5;

		// compute compressed size
		int dataSize = 0;
		int headerSize = palPtr ? 1080 : 36;
		switch (compType) {
		case 1:
			dataSize = wizPackType1(0, src, pvs->pitch, rCapt, tColor);
			break;
		case 0:
			dataSize = wizPackType0(0, src, pvs->pitch, rCapt, tColor);
			break;
		default:
			warning("unhandled compression type %d", compType);
			break;
		}

		// alignment
		dataSize = (dataSize + 1) & ~1;
		int wizSize = headerSize + dataSize;
		// write header
		uint8 *wizImg = createResource(resType, resNum, dataSize + headerSize);
		*(uint32 *)(wizImg + 0x00) = MKID('AWIZ');
		*(uint32 *)(wizImg + 0x04) = TO_BE_32(wizSize);
		*(uint32 *)(wizImg + 0x08) = MKID('WIZH');
		*(uint32 *)(wizImg + 0x0C) = TO_BE_32(0x14);
		*(uint32 *)(wizImg + 0x10) = TO_LE_32(compType);
		*(uint32 *)(wizImg + 0x14) = TO_LE_32(w);
		*(uint32 *)(wizImg + 0x18) = TO_LE_32(h);
		int curSize = 0x1C;
		if (palPtr) {
			*(uint32 *)(wizImg + 0x1C) = MKID('RGBS');
			*(uint32 *)(wizImg + 0x20) = TO_BE_32(0x308);
			memcpy(wizImg + 0x24, palPtr, 0x300);
			*(uint32 *)(wizImg + 0x324) = MKID('RMAP');
			*(uint32 *)(wizImg + 0x328) = TO_BE_32(0x10C);
			*(uint32 *)(wizImg + 0x32C) = 0;
			curSize = 0x330;
			for (int i = 0; i < 256; ++i) {
				wizImg[curSize] = i;
				++curSize;
			}
		}
		*(uint32 *)(wizImg + curSize + 0x0) = MKID('WIZD');
		*(uint32 *)(wizImg + curSize + 0x4) = TO_BE_32(dataSize + 8);
		curSize += 8;

		// write compressed data
		switch (compType) {
		case 1:
			wizPackType1(wizImg + headerSize, src, pvs->pitch, rCapt, tColor);
			break;
		case 0:
			wizPackType0(wizImg + headerSize, src, pvs->pitch, rCapt, tColor);
			break;
		default:
			break;
		}
	}
}

void ScummEngine_v72he::o72_captureWizImage() {
	Common::Rect grab;
	grab.bottom = pop() + 1;
	grab.right = pop() + 1;
	grab.top = pop();
	grab.left = pop();
	captureWizImage(rtImage, pop(), grab, false, true);	
}

void ScummEngine_v72he::o72_getTimer() {
	int timer = pop();
	int cmd = fetchScriptByte();

	if (cmd == 10 || cmd == 50) {
		checkRange(3, 1, timer, "o72_getTimer: Timer %d out of range(%d)");
		int diff = _system->getMillis() - _timers[timer];
		push(diff);
	} else {
		push(0);
	}
}

void ScummEngine_v72he::o72_setTimer() {
	int timer = pop();
	int cmd = fetchScriptByte();

	if (cmd == 158 || cmd == 61) {
		checkRange(3, 1, timer, "o72_setTimer: Timer %d out of range(%d)");
		_timers[timer] = _system->getMillis();
	} else {
		error("TIMER command %d?", cmd);
	}
}

void ScummEngine_v72he::o72_unknown5A() {
	// Seems to get length of sound already played
	int snd = pop();
	int r = _mixer->getChannelElapsedTime(_sound->_musicChannelHandle);

	push(r * 10);
	debug(1,"o72_unknown5A stub (%d)", snd);
}

void ScummEngine_v72he::o72_startScript() {
	int args[24];
	int script;
	byte flags;

	getStackList(args, ARRAYSIZE(args));
	script = pop();
	flags = fetchScriptByte();
	runScript(script, (flags == 199 || flags == 200), (flags == 195 || flags == 200), args);
}

void ScummEngine_v72he::o72_startObject() {
	int args[24];
	int script, entryp;
	byte flags;

	getStackList(args, ARRAYSIZE(args));
	entryp = pop();
	script = pop();
	flags = fetchScriptByte();
	runObjectScript(script, entryp, (flags == 199 || flags == 200), (flags == 195 || flags == 200), args);
}

void ScummEngine_v72he::o72_drawObject() {
	byte subOp = fetchScriptByte();
	int state = 0, y = -1, x = -1;

	switch (subOp) {
	case 62:
		state = pop();
		y = pop();
		x = pop();
		break;
	case 63:
		state = pop();
		if (state == 0)
			state = 1;
		break;
	case 65:
		state = 1;
		y = pop();
		x = pop();
		break;
	default:
		error("o72_drawObject: default case %d", subOp);
	}

	int object = pop();
	int objnum = getObjectIndex(object);
	if (objnum == -1)
		return;

	if (y != -1 && x != -1) {
		_objs[objnum].x_pos = x * 8;
		_objs[objnum].y_pos = y * 8;
	}

	if (state != -1) {
		addObjectToDrawQue(objnum);
		putState(object, state);
	}
}

void ScummEngine_v72he::o72_printWizImage() {
	WizImage wi;
	wi.resNum = pop();
	wi.x1 = wi.y1 = 0;
	wi.state = 0;
	wi.flags = 4;
	drawWizImage(rtImage, &wi);
}

void ScummEngine_v72he::o72_getArrayDimSize() {
	byte subOp = fetchScriptByte();
	int32 val1, val2;
	ArrayHeader *ah = (ArrayHeader *)getResourceAddress(rtString, readVar(fetchScriptWord()));
	if (!ah) {
		push(0);
		return;
	}

	switch (subOp) {
	case 1:
	case 3:
		val1 = FROM_LE_32(ah->dim1end);
		val2 = FROM_LE_32(ah->dim1start);
		push(val1 - val2 + 1);
		break;
	case 2:
		val1 = FROM_LE_32(ah->dim2end);
		val2 = FROM_LE_32(ah->dim2start);
		push(val1 - val2 + 1);
		break;
	case 4:
		push(FROM_LE_32(ah->dim1start));
		break;
	case 5:
		push(FROM_LE_32(ah->dim1end));
		break;
	case 6:
		push(FROM_LE_32(ah->dim2start));
		break;
	case 7:
		push(FROM_LE_32(ah->dim2end));
		break;
	default:
		error("o72_getArrayDimSize: default case %d", subOp);
	}
}

void ScummEngine_v72he::o72_getNumFreeArrays() {
	byte **addr = res.address[rtString];
	int i, num = 0;

	for (i = 1; i < _numArray; i++) {
		if (!addr[i])
			num++;
	}

	push (num);
}

void ScummEngine_v72he::o72_roomOps() {
	int a, b, c, d, e;
	byte op;
	byte filename[100];

	op = fetchScriptByte();

	switch (op) {
	case 172:		// SO_ROOM_SCROLL
		b = pop();
		a = pop();
		if (a < (_screenWidth / 2))
			a = (_screenWidth / 2);
		if (b < (_screenWidth / 2))
			b = (_screenWidth / 2);
		if (a > _roomWidth - (_screenWidth / 2))
			a = _roomWidth - (_screenWidth / 2);
		if (b > _roomWidth - (_screenWidth / 2))
			b = _roomWidth - (_screenWidth / 2);
		VAR(VAR_CAMERA_MIN_X) = a;
		VAR(VAR_CAMERA_MAX_X) = b;
		break;

	case 174:		// SO_ROOM_SCREEN
		b = pop();
		a = pop();
		initScreens(a, _screenHeight);
		break;

	case 175:		// SO_ROOM_PALETTE
		d = pop();
		c = pop();
		b = pop();
		a = pop();
		setPalColor(d, a, b, c);
		break;

	case 179:		// SO_ROOM_INTENSITY
		c = pop();
		b = pop();
		a = pop();
		darkenPalette(a, a, a, b, c);
		break;

	case 180:		// SO_ROOM_SAVEGAME
		_saveTemporaryState = true;
		_saveLoadSlot = pop();
		_saveLoadFlag = pop();
		break;

	case 181:		// SO_ROOM_FADE
		// Defaults to 1 but doesn't use fade effects
		a = pop();
		break;

	case 182:		// SO_RGB_ROOM_INTENSITY
		e = pop();
		d = pop();
		c = pop();
		b = pop();
		a = pop();
		darkenPalette(a, b, c, d, e);
		break;

	case 213:		// SO_ROOM_NEW_PALETTE
		a = pop();
		setPalette(a, _roomResource);
		break;

	case 220:
		a = pop();
		b = pop();
		copyPalColor(a, b);
		break;

	case 221:
		copyScriptString(filename);
		_saveLoadFlag = pop();
		_saveLoadSlot = 1;
		_saveTemporaryState = true;
		break;

	case 234:
		b = pop();
		a = pop();
		swapObjects(a, b);
		break;

	case 236:
		b = pop();
		a = pop();
		setPalette(a, b);
		break;

	default:
		error("o72_roomOps: default case %d", op);
	}
}

void ScummEngine_v72he::o72_actorOps() {
	Actor *a;
	int i, j, k;
	int args[32];
	byte subOp;
	byte string[256];

	subOp = fetchScriptByte();
	if (subOp == 197) {
		_curActor = pop();
		return;
	}

	a = derefActorSafe(_curActor, "o72_actorOps");
	if (!a)
		return;

	switch (subOp) {
	case 21: // HE 80+
		k = getStackList(args, ARRAYSIZE(args));
		for (i = 0; i < k; ++i) {
			a->setUserCondition(args[i] & 0x7F, args[i] & 0x80);
		}
		debug(1,"o72_actorOps: case 21 (%d)", k);
		break;
	case 24: // HE 80+
		k = pop();
		a->heNoTalkAnimation = 1;
		a->setTalkCondition(k);
		debug(1,"o72_actorOps: case 24 (%d)", k);
		break;
	case 43: // HE 90+
		// Uses reverse order of layering, so we adjust
		a->_layer = -pop();
		break;
	case 64:
	case 67: // HE 99+
		_actorClipOverride.bottom = pop();
		_actorClipOverride.right = pop();
		_actorClipOverride.top = pop();
		_actorClipOverride.left = pop();
		break;
	case 65: // HE 98+
		i = pop();
		j = pop();
		a->putActor(i, j, a->room);
		break;
	case 68: // HE 90+
		k = pop();
		debug(1,"o72_actorOps: case 68 (%d)", k);
		break;
	case 76:		// SO_COSTUME
		a->setActorCostume(pop());
		break;
	case 77:		// SO_STEP_DIST
		j = pop();
		i = pop();
		a->setActorWalkSpeed(i, j);
		break;
	case 78:		// SO_SOUND
		k = getStackList(args, ARRAYSIZE(args));
		for (i = 0; i < k; i++)
			a->sound[i] = args[i];
		break;
	case 79:		// SO_WALK_ANIMATION
		a->_walkFrame = pop();
		break;
	case 80:		// SO_TALK_ANIMATION
		a->_talkStopFrame = pop();
		a->_talkStartFrame = pop();
		break;
	case 81:		// SO_STAND_ANIMATION
		a->_standFrame = pop();
		break;
	case 82:		// SO_ANIMATION
		// dummy case in scumm6
		pop();
		pop();
		pop();
		break;
	case 83:		// SO_DEFAULT
		a->initActor(0);
		break;
	case 84:		// SO_ELEVATION
		a->setElevation(pop());
		break;
	case 85:		// SO_ANIMATION_DEFAULT
		a->_initFrame = 1;
		a->_walkFrame = 2;
		a->_standFrame = 3;
		a->_talkStartFrame = 4;
		a->_talkStopFrame = 5;
		break;
	case 86:		// SO_PALETTE
		j = pop();
		i = pop();
		checkRange(255, 0, i, "Illegal palette slot %d");
		a->remapActorPaletteColor(i, j);
		break;
	case 87:		// SO_TALK_COLOR
		a->talkColor = pop();
		break;
	case 88:		// SO_ACTOR_NAME
		copyScriptString(string);
		loadPtrToResource(rtActorName, a->number, string);
		break;
	case 89:		// SO_INIT_ANIMATION
		a->_initFrame = pop();
		break;
	case 91:		// SO_ACTOR_WIDTH
		a->width = pop();
		break;
	case 92:		// SO_SCALE
		i = pop();
		a->setScale(i, i);
		break;
	case 93:		// SO_NEVER_ZCLIP
		a->forceClip = 0;
		break;
	case 94:		// SO_ALWAYS_ZCLIP
		a->forceClip = pop();
		break;
	case 95:		// SO_IGNORE_BOXES
		a->ignoreBoxes = 1;
		a->forceClip = 0;
		if (a->isInCurrentRoom())
			a->putActor(a->_pos.x, a->_pos.y, a->room);
		break;
	case 96:		// SO_FOLLOW_BOXES
		a->ignoreBoxes = 0;
		a->forceClip = 0;
		if (a->isInCurrentRoom())
			a->putActor(a->_pos.x, a->_pos.y, a->room);
		break;
	case 97:		// SO_ANIMATION_SPEED
		a->setAnimSpeed(pop());
		break;
	case 98:		// SO_SHADOW
		a->_shadowMode = pop();
		debug(0, "Set actor XMAP idx to %d", a->_shadowMode);
		break;
	case 99:		// SO_TEXT_OFFSET
		a->talkPosY = pop();
		a->talkPosX = pop();
		break;
	case 156:		// HE 72+
		a->charset = pop();
		break;
	case 175:		// HE 99+
		a->hePaletteNum = pop();
		break;
	case 198:		// SO_ACTOR_VARIABLE
		i = pop();
		a->setAnimVar(pop(), i);
		break;
	case 215:		// SO_ACTOR_IGNORE_TURNS_ON
		a->ignoreTurns = true;
		break;
	case 216:		// SO_ACTOR_IGNORE_TURNS_OFF
		a->ignoreTurns = false;
		break;
	case 217:		// SO_ACTOR_NEW
		a->initActor(2);
		break;
	case 218:		
		{
			int top_actor = a->top;
			int bottom_actor = a->bottom;
			a->drawToBackBuf = true;
			a->needRedraw = true;
			a->drawActorCostume();
			a->drawToBackBuf = false;
			a->needRedraw = true;
			a->drawActorCostume();
			a->needRedraw = false;

			if (a->top > top_actor)
				a->top = top_actor;
			if (a->bottom < bottom_actor)
				a->bottom = bottom_actor;

		}
		break;
	case 219:
		a->drawToBackBuf = false;
		a->needRedraw = true;
		a->needBgReset = true;
		break;
	case 225:
		{
		copyScriptString(string);
		int slot = pop();

		int len = resStrLen(string) + 1;
		addMessageToStack(string, a->heTalkQueue[slot].sentence, len);

		a->heTalkQueue[slot].posX = a->talkPosX;
		a->heTalkQueue[slot].posY = a->talkPosY;
		a->heTalkQueue[slot].color = a->talkColor;
		break;
		}
	default:
		error("o72_actorOps: default case %d", subOp);
	}
}

void ScummEngine_v72he::o72_verbOps() {
	int slot, a, b;
	VerbSlot *vs;
	byte op;
	byte name[200];

	op = fetchScriptByte();
	if (op == 196) {
		_curVerb = pop();
		_curVerbSlot = getVerbSlot(_curVerb, 0);
		checkRange(_numVerbs - 1, 0, _curVerbSlot, "Illegal new verb slot %d");
		return;
	}
	vs = &_verbs[_curVerbSlot];
	slot = _curVerbSlot;
	switch (op) {
	case 124:		// SO_VERB_IMAGE
		a = pop();
		if (_curVerbSlot) {
			setVerbObject(_roomResource, a, slot);
			vs->type = kImageVerbType;
			vs->imgindex = a;
		}
		break;
	case 125:		// SO_VERB_NAME
		copyScriptString(name);
		loadPtrToResource(rtVerb, slot, name);
		vs->type = kTextVerbType;
		vs->imgindex = 0;
		break;
	case 126:		// SO_VERB_COLOR
		vs->color = pop();
		break;
	case 127:		// SO_VERB_HICOLOR
		vs->hicolor = pop();
		break;
	case 128:		// SO_VERB_AT
		vs->curRect.top = pop();
		vs->curRect.left = pop();
		break;
	case 129:		// SO_VERB_ON
		vs->curmode = 1;
		break;
	case 130:		// SO_VERB_OFF
		vs->curmode = 0;
		break;
	case 131:		// SO_VERB_DELETE
		slot = getVerbSlot(pop(), 0);
		killVerb(slot);
		break;
	case 132:		// SO_VERB_NEW
		slot = getVerbSlot(_curVerb, 0);
		if (slot == 0) {
			for (slot = 1; slot < _numVerbs; slot++) {
				if (_verbs[slot].verbid == 0)
					break;
			}
			if (slot == _numVerbs)
				error("Too many verbs");
			_curVerbSlot = slot;
		}
		vs = &_verbs[slot];
		vs->verbid = _curVerb;
		vs->color = 2;
		vs->hicolor = 0;
		vs->dimcolor = 8;
		vs->type = kTextVerbType;
		vs->charset_nr = _string[0]._default.charset;
		vs->curmode = 0;
		vs->saveid = 0;
		vs->key = 0;
		vs->center = 0;
		vs->imgindex = 0;
		break;
	case 133:		// SO_VERB_DIMCOLOR
		vs->dimcolor = pop();
		break;
	case 134:		// SO_VERB_DIM
		vs->curmode = 2;
		break;
	case 135:		// SO_VERB_KEY
		vs->key = pop();
		break;
	case 136:		// SO_VERB_CENTER
		vs->center = 1;
		break;
	case 137:		// SO_VERB_NAME_STR
		a = pop();
		if (a == 0) {
			loadPtrToResource(rtVerb, slot, (const byte *)"");
		} else {
			loadPtrToResource(rtVerb, slot, getStringAddress(a));
		}
		vs->type = kTextVerbType;
		vs->imgindex = 0;
		break;
	case 139:		// SO_VERB_IMAGE_IN_ROOM
		b = pop();
		a = pop();

		if (slot && a != vs->imgindex) {
			setVerbObject(b, a, slot);
			vs->type = kImageVerbType;
			vs->imgindex = a;
		}
		break;
	case 140:		// SO_VERB_BAKCOLOR
		vs->bkcolor = pop();
		break;
	case 255:
		drawVerb(slot, 0);
		verbMouseOver(0);
		break;
	default:
		error("o72_verbops: default case %d", op);
	}
}

void ScummEngine_v72he::o72_arrayOps() {
	byte subOp = fetchScriptByte();
	int array = fetchScriptWord();
	int offs, tmp, tmp2, tmp3;
	int dim1end, dim1start, dim2end, dim2start;
	int id, len, b, c;
	ArrayHeader *ah;
	int list[128];
	byte string[2048];


	debug(1,"o72_arrayOps: case %d", subOp);
	switch (subOp) {
	case 7:			// SO_ASSIGN_STRING
		copyScriptString(string);
		len = resStrLen(string) + 1;
		ah = defineArray(array, kStringArray, 0, 0, 0, len);
		memcpy(ah->data, string, len);
		break;

	case 126:
		len = getStackList(list, ARRAYSIZE(list));
		dim1end = pop();
		dim1start = pop();
		dim2end = pop();
		dim2start = pop();
		id = readVar(array);
		if (id == 0) {
			defineArray(array, kDwordArray, dim2start, dim2end, dim1start, dim1end);
		}
		tmp2 = 0;
		while (dim2start <= dim2end) {
			tmp = dim1start;
			while (tmp <= dim1end) {
				writeArray(array, dim2start, tmp, list[tmp2++]);
				if (tmp2 == len)
					tmp2 = 0;
				tmp++;
			}
			dim2start++;
		}
		break;
	case 127:
		{
		// TODO
		// Array copy and cat?
		//Array1
		dim1end = pop();
		dim1start = pop();
		dim2end = pop();
		dim2start = pop();
		//Array2
		array = fetchScriptWord();
		dim1end = pop();
		dim1start = pop();
		dim2end = pop();
		dim2start = pop();
		}
		break;
	case 128:
		b = pop();
		c = pop();
		dim1end = pop();
		dim1start = pop();
		dim2end = pop();
		dim2start = pop();
		id = readVar(array);
		if (id == 0) {
			defineArray(array, kDwordArray, dim2start, dim2end, dim1start, dim1end);
		}

		len = c - b;
		len |= dim2end;
		len = len - dim2end + 1;
		offs = (b >= c) ? 1 : -1;
		tmp2 = c;
		tmp3 = len;
		while (dim2start <= dim2end) {
			tmp = dim1start;
			while (tmp <= dim1end) {
				writeArray(array, dim2start, tmp, tmp2);
				if (--tmp3 == 0) {
					tmp2 = c;
					tmp3 = len;
				} else {
					tmp2 += offs;
				}
				tmp++;
			}
			dim2start++;
		}
		break;
	case 194:			// SO_ASSIGN_STRING
		decodeScriptString(string);
		len = resStrLen(string) + 1;
		ah = defineArray(array, kStringArray, 0, 0, 0, len);
		memcpy(ah->data, string, len);
		break;
	case 208:		// SO_ASSIGN_INT_LIST
		b = pop();
		c = pop();
		id = readVar(array);
		if (id == 0) {
			defineArray(array, kDwordArray, 0, 0, 0, b + c);
		}
		while (c--) {
			writeArray(array, 0, b + c, pop());
		}
		break;
	case 212:		// SO_ASSIGN_2DIM_LIST
		len = getStackList(list, ARRAYSIZE(list));
		id = readVar(array);
		if (id == 0)
			error("Must DIM a two dimensional array before assigning");
		c = pop();
		while (--len >= 0) {
			writeArray(array, c, len, list[len]);
		}
		break;
	default:
		error("o72_arrayOps: default case %d (array %d)", subOp, array);
	}
}

void ScummEngine_v72he::o72_dimArray() {
	int data;
	int type = fetchScriptByte();

	switch (type) {
	case 2:		// SO_BIT_ARRAY
		data = kBitArray;
		break;
	case 3:		// SO_NIBBLE_ARRAY
		data = kNibbleArray;
		break;
	case 4:		// SO_BYTE_ARRAY
		data = kByteArray;
		break;
	case 5:		// SO_INT_ARRAY
		data = kIntArray;
		break;
	case 6:
		data = kDwordArray;
		break;
	case 7:		// SO_STRING_ARRAY
		data = kStringArray;
		break;
	case 204:		// SO_UNDIM_ARRAY
		nukeArray(fetchScriptWord());
		return;
	default:
		error("o72_dimArray: default case %d", type);
	}

	defineArray(fetchScriptWord(), data, 0, 0, 0, pop());
}


void ScummEngine_v72he::o72_dim2dimArray() {
	int a, b, data;
	int type = fetchScriptByte();
	switch (type) {
	case 2:		// SO_BIT_ARRAY
		data = kBitArray;
		break;
	case 3:		// SO_NIBBLE_ARRAY
		data = kNibbleArray;
		break;
	case 4:		// SO_BYTE_ARRAY
		data = kByteArray;
		break;
	case 5:		// SO_INT_ARRAY
		data = kIntArray;
		break;
	case 6:		
		data = kDwordArray;
		break;
	case 7:		// SO_STRING_ARRAY
		data = kStringArray;
		break;
	default:
		error("o72_dim2dimArray: default case %d", type);
	}

	b = pop();
	a = pop();
	defineArray(fetchScriptWord(), data, 0, a, 0, b);
}

void ScummEngine_v72he::o72_traceStatus() {
	byte string[80];

	copyScriptString(string);
	pop();
}

void ScummEngine_v72he::displayWizImage(const WizImage *pwi) {
	if (_fullRedraw) {
		assert(_wizImagesNum < ARRAYSIZE(_wizImages));
		memcpy(&_wizImages[_wizImagesNum], pwi, sizeof(WizImage));
		++_wizImagesNum;
	} else if (pwi->flags & 0x40) {
		drawWizPolygon(pwi->resNum, pwi->state, pwi->x1, pwi->flags);
	} else {
		drawWizImage(rtImage, pwi);
	}
}

void ScummEngine_v72he::getWizImageDim(int resnum, int state, int32 &w, int32 &h) {
	const uint8 *dataPtr = getResourceAddress(rtImage, resnum);
	if (dataPtr) {
		const uint8 *wizh = findWrappedBlock(MKID('WIZH'), dataPtr, state, 0);
		w = READ_LE_UINT32(wizh + 0x4);
		h = READ_LE_UINT32(wizh + 0x8);
	} else {
		w = 0;
		h = 0;
	}
}

uint8 *ScummEngine_v72he::drawWizImage(int restype, const WizImage *pwi) {
	debug(1, "drawWizImage(%d, %d, %d, %d, 0x%X)", restype, pwi->resNum, pwi->x1, pwi->y1, pwi->flags);
	uint8 *dst = NULL;
	const uint8 *dataPtr = getResourceAddress(restype, pwi->resNum);
	if (dataPtr) {
		const uint8 *rmap = NULL;
		const uint8 *xmap = findWrappedBlock(MKID('XMAP'), dataPtr, pwi->state, 0);
		
		const uint8 *wizh = findWrappedBlock(MKID('WIZH'), dataPtr, pwi->state, 0);
		assert(wizh);
		uint32 comp   = READ_LE_UINT32(wizh + 0x0);
		uint32 width  = READ_LE_UINT32(wizh + 0x4);
		uint32 height = READ_LE_UINT32(wizh + 0x8);
		assert(comp == 0 || comp == 1 || comp == 2 || comp == 3 || comp == 10 || comp == 11);
		
		const uint8 *wizd = findWrappedBlock(MKID('WIZD'), dataPtr, pwi->state, 0);
		assert(wizd);
		if (pwi->flags & 1) {
			const uint8 *pal = findWrappedBlock(MKID('RGBS'), dataPtr, pwi->state, 0);
			assert(pal);
			setPaletteFromPtr(pal, 256);
		}
		if (pwi->flags & 2) {
			rmap = findWrappedBlock(MKID('RMAP'), dataPtr, pwi->state, 0);
			assert(rmap);
			const uint8 *rgbs = findWrappedBlock(MKID('RGBS'), dataPtr, pwi->state, 0);
			assert(rgbs);
			warning("drawWizImage() unhandled flag 0x2");
			// XXX modify 'RMAP' buffer
		}
		if (pwi->flags & 4) {
			warning("WizImage printing is unimplemented");
			return NULL;
		}
		uint32 cw, ch;
		if (pwi->flags & 0x20) {
			dst = (uint8 *)malloc(width * height);
			int color = 255; // FIXME: should be (VAR_WIZ_TCOLOR != 0xFF) ? VAR(VAR_WIZ_TCOLOR) : 5;
			memset(dst, color, width * height);
			cw = width;
			ch = height;
		} else {
			VirtScreen *pvs = &virtscr[kMainVirtScreen];
			if (pwi->flags & 0x10) {
				dst = pvs->getPixels(0, pvs->topline);
			} else {
				dst = pvs->getBackPixels(0, pvs->topline);
			}
			cw = pvs->w;
			ch = pvs->h;
		}
		Common::Rect rScreen(cw, ch);
		// XXX handle 'XMAP' / 'RMAP' data
		if (comp == 1) {
			if (pwi->flags & 0x80) {
				warning("drawWizImage() unhandled flag 0x80");
			} else if (pwi->flags & 0x100) {
				warning("drawWizImage() unhandled flag 0x100");
			} else {
				gdi.copyWizImage(dst, wizd, cw, ch, pwi->x1, pwi->y1, width, height, &rScreen);
			}
		} else if (comp == 0 || comp == 2) {
			const uint8 *trns = findWrappedBlock(MKID('TRNS'), dataPtr, pwi->state, 0);
			int color = (trns == NULL) ? VAR(VAR_WIZ_TCOLOR) : -1;
			const uint8 *pal = xmap;
			if (pwi->flags & 2) {
				pal = rmap + 4;
			}
			gdi.copyRawWizImage(dst, wizd, cw, ch, pwi->x1, pwi->y1, width, height, &rScreen, pwi->flags, pal, color);
		} else {
			warning("unhandled wiz compression type %d", comp);
		}

		if (!(pwi->flags & 0x20)) {
			Common::Rect rImage(pwi->x1, pwi->y1, pwi->x1 + width, pwi->y1 + height);
			if (rImage.intersects(rScreen)) {
				rImage.clip(rScreen);
				if (!(pwi->flags & 8) && pwi->flags & 0x18) {
					++rImage.bottom;
					markRectAsDirty(kMainVirtScreen, rImage);
				} else {
					gdi.copyVirtScreenBuffers(rImage);
				}
			}
		}
	}
	return dst;
}

struct PolygonDrawData {
	struct InterArea {
		bool valid;
		int32 xmin;
		int32 xmax;
		int32 x1;
		int32 y1;
		int32 x2;
		int32 y2;
	};
	Common::Point pto;
	InterArea *ia;
	int areasNum;
	
	PolygonDrawData(int n) {
		areasNum = n;
		ia = new InterArea[areasNum];
		memset(ia, 0, sizeof(InterArea) * areasNum);
	}
	
	~PolygonDrawData() {
		delete[] ia;
	}
	
	void calcIntersection(const Common::Point *p1, const Common::Point *p2, const Common::Point *p3, const Common::Point *p4) {
		int32 x1_acc = p1->x << 0x10;
		int32 x3_acc = p3->x << 0x10;
		int32 y3_acc = p3->y << 0x10;
  		uint16 dy = ABS(p2->y - p1->y) + 1;
  		int32 x1_step = ((p2->x - p1->x) << 0x10) / dy;
  		int32 x3_step = ((p4->x - p3->x) << 0x10) / dy;
  		int32 y3_step = ((p4->y - p3->y) << 0x10) / dy;

  		int iaidx = p1->y - pto.y;
  		while (dy--) {
  			assert(iaidx >= 0 && iaidx < areasNum);
  			InterArea *pia = &ia[iaidx];
  			int32 tx1 = x1_acc >> 0x10;
  			int32 tx3 = x3_acc >> 0x10;
  			int32 ty3 = y3_acc >> 0x10;
  			
  			if (!pia->valid || pia->xmin > tx1) {
  				pia->xmin = tx1;
  				pia->x1 = tx3;
  				pia->y1 = ty3;
			}
  			if (!pia->valid || pia->xmax < tx1) {
  				pia->xmax = tx1;
  				pia->x2 = tx3;
  				pia->y2 = ty3;
			}
  			pia->valid = true;

  			x1_acc += x1_step;
  			x3_acc += x3_step;
  			y3_acc += y3_step;
  			
  			if (p2->y <= p1->y) {
  				--iaidx;
  			} else {
  				++iaidx;
  			}  			
  		}
	}
};

void ScummEngine_v72he::drawWizPolygon(int resnum, int state, int id, int flags) {
	int i;
	WizPolygon *wp = NULL;
	for (i = 0; i < _wizNumPolygons; ++i) {
		if (_wizPolygons[i].id == id) {
			wp = &_wizPolygons[i];
			break;
		}
	}
	if (!wp) {
		error("Polygon %d is not defined", id);
	}
	if (wp->numVerts != 5) {
		error("Invalid point count %d for Polygon %d", wp->numVerts, id);
	}
	WizImage wi;
	wi.resNum = resnum;
	wi.state = state;
	wi.x1 = wi.y1 = 0;
	wi.flags = 0x20;
	uint8 *srcWizBuf = drawWizImage(rtImage, &wi);
	if (srcWizBuf) {
		uint8 *dst;
		VirtScreen *pvs = &virtscr[kMainVirtScreen];
		if (flags & 0x10) {
			dst = pvs->getPixels(0, 0);
		} else {
			dst = pvs->getBackPixels(0, 0);
		}
		if (wp->bound.left < 0 || wp->bound.top < 0 || wp->bound.right >= pvs->w || wp->bound.bottom >= pvs->h) {
			error("Invalid coords polygon %d", wp->id);
		}

		int32 wizW, wizH;
		getWizImageDim(resnum, state, wizW, wizH);
		Common::Point bbox[4];
		bbox[0].x = 0;
		bbox[0].y = 0;
		bbox[1].x = wizW - 1;
		bbox[1].y = 0;
		bbox[2].x = wizW - 1;
		bbox[2].y = wizH - 1;
		bbox[3].x = 0;
		bbox[3].y = wizH - 1;

  		int16 xmin_p, xmax_p, ymin_p, ymax_p;
  		xmin_p = xmax_p = wp->vert[0].x;
  		ymin_p = ymax_p = wp->vert[0].y;
  		for (i = 1; i < 4; ++i) {
  			xmin_p = MIN(wp->vert[i].x, xmin_p);
  			xmax_p = MAX(wp->vert[i].x, xmax_p);
  			ymin_p = MIN(wp->vert[i].y, ymin_p);
  			ymax_p = MAX(wp->vert[i].y, ymax_p);
  		}
  		
  		int16 xmin_b, xmax_b, ymin_b, ymax_b;
  		xmin_b = 0;
  		xmax_b = wizW - 1;
  		ymin_b = 0;
  		ymax_b = wizH - 1;

		PolygonDrawData pdd(ymax_p - ymin_p + 1);
		pdd.pto.x = xmin_p;
		pdd.pto.y = ymin_p;
		
		for (i = 0; i < 3; ++i) {
			pdd.calcIntersection(&wp->vert[i], &wp->vert[i + 1], &bbox[i], &bbox[i + 1]);
		}
		pdd.calcIntersection(&wp->vert[3], &wp->vert[0], &bbox[3], &bbox[0]);
				
		uint yoff = pdd.pto.y * pvs->w;
		for (i = 0; i < pdd.areasNum; ++i) {
			PolygonDrawData::InterArea *pia = &pdd.ia[i];
			uint16 dx = pia->xmax - pia->xmin + 1;
			uint8 *dstPtr = dst + pia->xmin + yoff;
			int32 x_acc = pia->x1 << 0x10;
			int32 y_acc = pia->y1 << 0x10;
			int32 x_step = ((pia->x2 - pia->x1) << 0x10) / dx;
			int32 y_step = ((pia->y2 - pia->y1) << 0x10) / dx;
			while (dx--) {
				uint srcWizOff = (y_acc >> 0x10) * wizW + (x_acc >> 0x10);
				assert(srcWizOff < (uint32)(wizW * wizH));
				x_acc += x_step;
				y_acc += y_step;
				*dstPtr++ = srcWizBuf[srcWizOff];
			}
			yoff += pvs->pitch;
		}

		if (flags & 0x10) {
			markRectAsDirty(kMainVirtScreen, wp->bound);
		} else {
			gdi.copyVirtScreenBuffers(wp->bound);
		}

		free(srcWizBuf);
	}
}

void ScummEngine_v72he::flushWizBuffer() {
	for (int i = 0; i < _wizImagesNum; ++i) {
		WizImage *pwi = &_wizImages[i];
		if (pwi->flags & 0x40) {
			drawWizPolygon(pwi->resNum, pwi->state, pwi->x1, pwi->flags);
		} else {
			drawWizImage(rtImage, pwi);
		}
	}
	_wizImagesNum = 0;
}

void ScummEngine_v72he::o72_kernelGetFunctions() {
	int args[29];
	int retval;
	ArrayHeader *ah;
	getStackList(args, ARRAYSIZE(args));

	switch (args[0]) {
	case 1:
		writeVar(0, 0);
		defineArray(0, kByteArray, 0, 0, 0, virtScreenSave(0, args[1], args[2], args[3], args[4]));
		retval = readVar(0);
		ah = (ArrayHeader *)getResourceAddress(rtString, retval);
		virtScreenSave(ah->data, args[1], args[2], args[3], args[4]);
		push(retval);
		break;
	case 1001:
		{
		double a = args[1] * PI / 180.;
		push((int)(sin(a) * 100000));
		}
		break;
	case 1002:
		{
		double a = args[1] * PI / 180.;
		push((int)(cos(a) * 100000));
		}
		break;
	default:
		error("o72_kernelGetFunctions: default case %d", args[0]);
	}
}

void ScummEngine_v72he::o72_drawWizImage() {
	WizImage wi;
	wi.flags = pop();
	wi.y1 = pop();
	wi.x1 = pop();
	wi.resNum = pop();
	wi.state = 0;
	displayWizImage(&wi);
}

void ScummEngine_v72he::o72_unknownCF() {
	byte string[255];

	copyScriptString(string);
	writeVar(0, 0);
	defineArray(0, kStringArray, 0, 0, 0, 0);
	writeArray(0, 0, 0, 0);
	push(readVar(0));
	debug(1,"o72_unknownCF: String %s", string);
}

void ScummEngine_v72he::o72_jumpToScript() {
	int args[24];
	int script;
	byte flags;

	getStackList(args, ARRAYSIZE(args));
	script = pop();
	flags = fetchScriptByte();
	stopObjectCode();
	runScript(script, (flags == 199 || flags == 200), (flags == 195 || flags == 200), args);
}

void ScummEngine_v72he::o72_openFile() {
	int mode, slot, l, r;
	byte filename[256];

	mode = pop();
	copyScriptString(filename);

	// HACK Correct incorrect filenames
	if (!strcmp((char *)filename,".he3")) {
		memset(filename, 0, sizeof(filename));
		sprintf((char *)filename, "%s.he3", _gameName.c_str());
	} else if (!strcmp((char *)filename,".he7")) {
		memset(filename, 0, sizeof(filename));
		sprintf((char *)filename, "%s.he7", _gameName.c_str());
	} else if (!strcmp((char *)filename,".HE9")) {
		memset(filename, 0, sizeof(filename));
		sprintf((char *)filename, "%s.he9", _gameName.c_str());
	}

	for (r = strlen((char*)filename); r != 0; r--) {
		if (filename[r - 1] == '\\')
			break;
	}
	
	debug(0,"Filename %s", filename + r);

	// HACK: Convert paths
	if (filename[0] == ':') {
		int len = resStrLen(filename);
		int i = 1, j = 0;
		while(len--) {
			if (filename[i] == ':')
				filename[j] = '/';
			else
				filename[j] = filename[i];

			i++;
			j++;
		}
		debug(0,"Converted Filename %s", filename + r);
	}

	slot = -1;
	for (l = 0; l < 17; l++) {
		if (_hFileTable[l].isOpen() == false) {
			slot = l;
			break;
		}
	}

	if (slot != -1) {
		if (mode == 1)
			_hFileTable[slot].open((char*)filename + r, File::kFileReadMode);
		else if (mode == 2)
			_hFileTable[slot].open((char*)filename + r, File::kFileWriteMode);
		else
			error("o72_openFile(): wrong open file mode %d", mode);

		if (_hFileTable[slot].isOpen() == false)
			slot = -1;

	}
	debug(1, "o72_openFile: slot %d, mode %d", slot, mode);
	push(slot);
}

int ScummEngine_v72he::readFileToArray(int slot, int32 size) {
	if (size == 0)
		size = _hFileTable[slot].size() - _hFileTable[slot].pos();

	writeVar(0, 0);

	ArrayHeader *ah = defineArray(0, kByteArray, 0, 0, 0, size);
	_hFileTable[slot].read(ah->data, size);

	return readVar(0);
}

void ScummEngine_v72he::o72_readFile() {
	int slot, val;
	int32 size;
	byte subOp = fetchScriptByte();

	switch (subOp) {
	case 4:
		slot = pop();
		val = _hFileTable[slot].readByte();
		push(val);
		break;
	case 5:
		slot = pop();
		val = _hFileTable[slot].readUint16LE();
		push(val);
		break;
	case 6:
		slot = pop();
		val = _hFileTable[slot].readUint32LE();
		push(val);
		break;
	case 8:
		fetchScriptByte();
		size = pop();
		slot = pop();
		val = readFileToArray(slot, size);
		push(val);
		break;
	default:
		error("o72_readFile: default case %d", subOp);
	}
	debug(1, "o72_readFile: slot %d, subOp %d val %d", slot, subOp, val);
}

void ScummEngine_v72he::writeFileFromArray(int slot, int resID) {
	ArrayHeader *ah = (ArrayHeader *)getResourceAddress(rtString, resID);
	int32 size = (FROM_LE_32(ah->dim1end) - FROM_LE_32(ah->dim1start) + 1) *
		(FROM_LE_32(ah->dim2end) - FROM_LE_32(ah->dim2start) + 1);

	_hFileTable[slot].write(ah->data, size);
}

void ScummEngine_v72he::o72_writeFile() {
	int16 resID = pop();
	int slot = pop();
	byte subOp = fetchScriptByte();

	switch (subOp) {
	case 4:
		_hFileTable[slot].writeByte(resID);
		break;
	case 5:
		_hFileTable[slot].writeUint16LE(resID);
		break;
	case 6:
		_hFileTable[slot].writeUint32LE(resID);
		break;
	case 8:
		fetchScriptByte();
		writeFileFromArray(slot, resID);
		break;
	default:
		error("o72_writeFile: default case %d", subOp);
	}
	debug(1, "o72_writeFile: slot %d, subOp %d", slot, subOp);
}

void ScummEngine_v72he::o72_findAllObjects() {
	int room = pop();
	int i = 1;

	if (room != _currentRoom)
		warning("o72_findAllObjects: current room is not %d", room);
	writeVar(0, 0);
	defineArray(0, kDwordArray, 0, 0, 0, _numLocalObjects + 1);
	writeArray(0, 0, 0, _numLocalObjects);
	
	while (i < _numLocalObjects) {
		writeArray(0, 0, i, _objs[i].obj_nr);
		i++;
	}
	
	push(readVar(0));
}

void ScummEngine_v72he::o72_deleteFile() {
	byte filename[100];
	int r;

	copyScriptString(filename);
	for (r = strlen((char*)filename); r != 0; r--) {
		if (filename[r - 1] == '\\')
			break;
	}

	debug(1, "stub o72_deleteFile(%s)", filename + r);
}

void ScummEngine_v72he::o72_getPixel() {
	byte area;

	int y = pop();
	int x = pop();
	byte subOp = fetchScriptByte();

	VirtScreen *vs = findVirtScreen(y);
	if (vs == NULL || x > _screenWidth - 1 || x < 0) {
		push(-1);
		return;
	}

	switch (subOp) {
	case 9: // HE 100
	case 218:
		area = *vs->getBackPixels(x, y - vs->topline);
		break;
	case 8: // HE 100
	case 219:
		area = *vs->getPixels(x, y - vs->topline);
		break;
	default:
		error("o72_getPixel: default case %d", subOp);
	}
	push(area);
}

void ScummEngine_v72he::o72_pickVarRandom() {
	int num;
	int args[100];
	int32 dim1end;

	num = getStackList(args, ARRAYSIZE(args));
	int value = fetchScriptWord();

	if (readVar(value) == 0) {
		defineArray(value, kDwordArray, 0, 0, 0, num);
		if (num > 0) {
			int16 counter = 0;
			do {
				writeArray(value, 0, counter + 1, args[counter]);
			} while (++counter < num);
		}

		shuffleArray(value, 1, num);
		writeArray(value, 0, 0, 2);
		push(readArray(value, 0, 1));
		return;
	}

	num = readArray(value, 0, 0);

	ArrayHeader *ah = (ArrayHeader *)getResourceAddress(rtString, readVar(value));
	dim1end = FROM_LE_32(ah->dim1end);

	if (dim1end < num) {
		int16 var_2 = readArray(value, 0, num - 1);
		shuffleArray(value, 1, dim1end);
		if (readArray(value, 0, 1) == var_2) {
			num = 2;
		} else {
			num = 1;
		}
	}

	writeArray(value, 0, 0, num + 1);
	push(readArray(value, 0, num));
}

void ScummEngine_v72he::o72_redimArray() {
	int newX, newY;
	newY = pop();
	newX = pop();

	byte subOp = fetchScriptByte();
	switch (subOp) {
	case 5:
		redimArray(fetchScriptWord(), 0, newX, 0, newY, kIntArray);
		break;
	case 4:
		redimArray(fetchScriptWord(), 0, newX, 0, newY, kByteArray);
		break;
	case 6:
		redimArray(fetchScriptWord(), 0, newX, 0, newY, kDwordArray);
		break;
	default:
		error("o72_redimArray: default type %d", subOp);
	}
}

void ScummEngine_v72he::redimArray(int arrayId, int newDim2start, int newDim2end, 
								   int newDim1start, int newDim1end, int type) {
	int newSize, oldSize;

	if (readVar(arrayId) == 0)
		error("redimArray: Reference to zeroed array pointer");

	ArrayHeader *ah = (ArrayHeader *)getResourceAddress(rtString, readVar(arrayId));

	if (!ah)
		error("redimArray: Invalid array (%d) reference", readVar(arrayId));

	newSize = arrayDataSizes[type];
	oldSize = arrayDataSizes[FROM_LE_32(ah->type)];

	newSize *= (newDim1end - newDim1start + 1) * (newDim2end - newDim2start + 1);
	oldSize *= (FROM_LE_32(ah->dim1end) - FROM_LE_32(ah->dim1start) + 1) *
		(FROM_LE_32(ah->dim2end) - FROM_LE_32(ah->dim2start) + 1);

	newSize >>= 3;
	oldSize >>= 3;

	if (newSize != oldSize)
		error("redimArray: array %d redim mismatch", readVar(arrayId));

	ah->type = TO_LE_32(type);
	ah->dim1start = TO_LE_32(newDim1start);
	ah->dim1end = TO_LE_32(newDim1end);
	ah->dim2start = TO_LE_32(newDim2start);
	ah->dim2end = TO_LE_32(newDim2end);
}

void ScummEngine_v72he::o72_copyString() {
	int dst, size;
	int src = pop();

	size = resStrLen(getStringAddress(src)) + 1;

	writeVar(0, 0);
	defineArray(0, kStringArray, 0, 0, 0, size);
	writeArray(0, 0, 0, 0);
	dst = readVar(0);

	appendSubstring(dst, src, -1, -1);

	push(dst);
	debug(1,"stub o72_copyString");
}

void ScummEngine_v72he::o72_concatString() {
	int dst, size;

	int src2 = pop();
	int src1 = pop();

	size = resStrLen(getStringAddress(src1));
	size += resStrLen(getStringAddress(src2)) + 1;

	writeVar(0, 0);
	defineArray(0, kStringArray, 0, 0, 0, size);
	writeArray(0, 0, 0, 0);

	dst = readVar(0);

	appendSubstring(dst, src1, 0, -1);
	appendSubstring(dst, src2, 0, -1);

	push(dst);
	debug(1,"stub o72_concatString");
}

void ScummEngine_v72he::o72_checkGlobQueue() {
	byte subOp = fetchScriptByte();
	int idx = pop();

	debug(1,"o72_checkGlobQueue stub (%d, %d)", subOp, idx);
	push(100);
}

void ScummEngine_v72he::o72_readINI() {
	byte option[100];
	int type, retval;

	// we pretend that we don't have .ini file
	copyScriptString(option);
	type = fetchScriptByte();

	switch (type) {
	case 43: // HE 100
	case 6: // number
		if (!strcmp((char *)option, "NoPrinting"))
			push(1);
		else
			push(0);
		break;
	case 77: // HE 100
	case 7: // string
		writeVar(0, 0);
		defineArray(0, kStringArray, 0, 0, 0, 0);
		retval = readVar(0);
		writeArray(0, 0, 0, 0);
		push(retval); // var ID string
		break;
	default:
		error("o72_readINI: default type %d", type);
	}
	debug(1, "o72_readINI (%d) %s", type, option);
}

void ScummEngine_v72he::o72_writeINI() {
	int type, value;
	byte option[256], string[1024];

	type = fetchScriptByte();

	switch (type) {
	case 43: // HE 100
	case 6: // number
		value = pop();
		copyScriptString(option);
		debug(1,"o72_writeINI: %s set to %d", option, value);
		break;
	case 77: // HE 100
	case 7: // string
		copyScriptString(string);
		copyScriptString(option);
		debug(1,"o72_writeINI: %s set to %s", option, string);
		break;
	default:
		error("o72_writeINI: default type %d", type);
	}
}

void ScummEngine_v72he::o72_getResourceSize() {
	int size = 0, type;

	int idx = pop();
	byte subOp = fetchScriptByte();

	switch (subOp) {
	case 13:
		if (idx > _numSounds) {
			// TODO Music resource size
			push(100);
			return;
		}
		type = rtSound;
		break;
	case 14:
		type = rtRoomImage;
		break;
	case 15:
		type = rtImage;
		break;
	case 16:
		type = rtCostume;
		break;
	case 17:
		type = rtScript;
		break;
	default:
		warning("o72_getResourceSize: default type %d", subOp);
		push(0);
		return;
	}

	const byte *ptr = getResourceAddress(type, idx);
	if (ptr)
		size = READ_BE_UINT32(ptr + 4) - 8;
	push(size);
}

void ScummEngine_v72he::o72_setFilePath() {
	// File related
	byte filename[100];
	copyScriptString(filename);
	debug(1,"o72_setFilePath: %s", filename);
}

void ScummEngine_v72he::o72_setWindowCaption() {
	byte name[100];
	copyScriptString(name);
	int id = fetchScriptByte();

	debug(1,"o72_setWindowCaption: (%d) %s", id, name);
}

void ScummEngine_v72he::decodeParseString(int m, int n) {
	int i, colors, size;
	int args[31];
	byte name[1024];

	byte b = fetchScriptByte();

	switch (b) {
	case 65:		// SO_AT
		_string[m].ypos = pop();
		_string[m].xpos = pop();
		_string[m].overhead = false;
		break;
	case 66:		// SO_COLOR
		_string[m].color = pop();
		break;
	case 67:		// SO_CLIPPED
		_string[m].right = pop();
		break;
	case 69:		// SO_CENTER
		_string[m].center = true;
		_string[m].overhead = false;
		break;
	case 71:		// SO_LEFT
		_string[m].center = false;
		_string[m].overhead = false;
		break;
	case 72:		// SO_OVERHEAD
		_string[m].overhead = true;
		_string[m].no_talk_anim = false;
		break;
	case 73:		// SO_SAY_VOICE
		error("decodeParseString: case 73");
		break;
	case 74:		// SO_MUMBLE
		_string[m].no_talk_anim = true;
		break;
	case 75:		// SO_TEXTSTRING
		printString(m, _scriptPointer);
		_scriptPointer += resStrLen(_scriptPointer) + 1;
		break;
	case 194:
		decodeScriptString(name, true);
		printString(m, name);
		break;
	case 0xE1:
		{
		const byte *dataPtr = getResourceAddress(rtTalkie, pop());
		const byte *text = findWrappedBlock(MKID('TEXT'), dataPtr, 0, 0);
		size = getResourceDataSize(text);
		memcpy(name, text, size);
		printString(m, name);
		}
		break;
	case 0xF9:
		colors = pop();
		if (colors == 1) {
			_string[m].color = pop();
		} else {	
			push(colors);
			getStackList(args, ARRAYSIZE(args));
			for (i = 0; i < 16; i++)
				_charsetColorMap[i] = _charsetData[_string[1]._default.charset][i] = (unsigned char)args[i];
			_string[m].color = _charsetColorMap[0];
		}
		break;
	case 0xFE:
		_string[m].loadDefault();
		if (n)
			_actorToPrintStrFor = pop();
		break;
	case 0xFF:
		_string[m].saveDefault();
		break;
	default:
		error("decodeParseString: default case 0x%x", b);
	}
}

} // End of namespace Scumm