コード例 #1
0
int Logic::runScript(byte *scriptData, byte *objectData, uint32 offset) {
	byte pc[4];

	WRITE_LE_UINT32(pc, offset);
	return runScript2(scriptData, objectData, pc);
}
コード例 #2
0
ファイル: interpreter.cpp プロジェクト: St0rmcrow/scummvm
int Logic::runScript2(byte *scriptData, byte *objectData, byte *offsetPtr) {
	int i;

	// Interestingly, unlike our BASS engine the stack is a local variable.
	// I don't know whether or not this is relevant to the working of the
	// BS2 engine.

	Common::FixedStack<int32, STACK_SIZE> stack;
	int32 opcodeParams[STACK_SIZE];

	uint32 offset = READ_LE_UINT32(offsetPtr);

	ResHeader header;

	header.read(scriptData);

	scriptData += ResHeader::size() + ObjectHub::size();

	// The script data format:
	//	int32_TYPE	1		Size of variable space in bytes
	//	...				The variable space
	//	int32_TYPE	1		numberOfScripts
	//	int32_TYPE	numberOfScripts	The offsets for each script

	// Initialise some stuff

	uint32 ip = 0;			 // Code pointer
	int scriptNumber;

	// Get the start of variables and start of code

	byte *localVars = scriptData + 4;
	byte *code = scriptData + READ_LE_UINT32(scriptData) + 4;
	uint32 noScripts = READ_LE_UINT32(code);

	code += 4;

	byte *offsetTable = code;

	if (offset < noScripts) {
		ip = READ_LE_UINT32(offsetTable + offset * 4);
		scriptNumber = offset;
		debug(8, "Starting script %d from %d", scriptNumber, ip);
	} else {
		ip = offset;

		for (i = 1; i < (int)noScripts; i++) {
			if (READ_LE_UINT32(offsetTable + 4 * i) >= ip)
				break;
		}

		scriptNumber = i - 1;
		debug(8, "Resuming script %d from %d", scriptNumber, ip);
	}

	// There are a couple of known script bugs related to interacting with
	// certain objects. We try to work around a few of them.

	bool checkMopBug = false;
	bool checkPyramidBug = false;
	bool checkElevatorBug = false;
	bool checkPearlBug = false;

	if (scriptNumber == 2) {
		if (strcmp((char *)header.name, "mop_73") == 0)
			checkMopBug = true;
		else if (strcmp((char *)header.name, "titipoco_81") == 0)
			checkPyramidBug = true;
		else if (strcmp((char *)header.name, "lift_82") == 0)
			checkElevatorBug = true;
		else if (strcmp((char *)header.name, "pearl_31") == 0)
			checkPearlBug = true;
	}

	code += noScripts * 4;

	// Code should now be pointing at an identifier and a checksum
	byte *checksumBlock = code;

	code += 4 * 3;

	if (READ_LE_UINT32(checksumBlock) != 12345678) {
		error("Invalid script in object %s", header.name);
		//return 0;
	}

	int32 codeLen = READ_LE_UINT32(checksumBlock + 4);
	int32 checksum = 0;

	for (i = 0; i < codeLen; i++)
		checksum += (unsigned char)code[i];

	if (checksum != (int32)READ_LE_UINT32(checksumBlock + 8)) {
		debug(1, "Checksum error in object %s", header.name);
		// This could be bad, but there has been a report about someone
		// who had problems running the German version because of
		// checksum errors. Could there be a version where checksums
		// weren't properly calculated?
	}

	bool runningScript = true;

	int parameterReturnedFromMcodeFunction = 0;	// Allow scripts to return things
	int savedStartOfMcode = 0;	// For saving start of mcode commands

	while (runningScript) {
		int32 a, b;
		int curCommand, parameter, value; // Command and parameter variables
		int retVal;
		int caseCount;
		bool foundCase;
		byte *ptr;

		curCommand = code[ip++];

		switch (curCommand) {

		// Script-related opcodes

		case CP_END_SCRIPT:
			// End the script
			runningScript = false;

			// WORKAROUND: The dreaded pyramid bug makes the torch
			// untakeable when you speak to Titipoco. This is
			// because one of the conditions for the torch to be
			// takeable is that Titipoco isn't doing anything out
			// of the ordinary. Global variable 913 has to be 0 to
			// signify that he is in his "idle" state.
			//
			// Unfortunately, simply the act of speaking to him
			// sets variable 913 to 1 (probably to stop him from
			// turning around every now and then). The script may
			// then go on to set the variable to different values
			// to trigger various behaviours in him, but if you
			// have run out of these cases the script won't ever
			// set it back to 0 again.
			//
			// So if his click hander finishes, and variable 913 is
			// 1, we set it back to 0 manually.

			if (checkPyramidBug && readVar(913) == 1) {
				warning("Working around pyramid bug: Resetting Titipoco's state");
				writeVar(913, 0);
			}

			// WORKAROUND: The not-so-known-but-should-be-dreaded
			// elevator bug.
			//
			// The click handler for the top of the elevator only
			// handles using the elevator, not examining it. When
			// examining it, the mouse cursor is removed but never
			// restored.

			if (checkElevatorBug && readVar(RIGHT_BUTTON)) {
				warning("Working around elevator bug: Restoring mouse pointer");
				fnAddHuman(NULL);
			}

			debug(9, "CP_END_SCRIPT");
			break;
		case CP_QUIT:
			// Quit out for a cycle
			WRITE_LE_UINT32(offsetPtr, ip);
			debug(9, "CP_QUIT");
			return 0;
		case CP_TERMINATE:
			// Quit out immediately without affecting the offset
			// pointer
			debug(9, "CP_TERMINATE");
			return 3;
		case CP_RESTART_SCRIPT:
			// Start the script again
			ip = FROM_LE_32(offsetTable[scriptNumber]);
			debug(9, "CP_RESTART_SCRIPT");
			break;

		// Stack-related opcodes

		case CP_PUSH_INT32:
			// Push a long word value on to the stack
			Read32ip(parameter);
			stack.push(parameter);
			debug(9, "CP_PUSH_INT32: %d", parameter);
			break;
		case CP_PUSH_LOCAL_VAR32:
			// Push the contents of a local variable
			Read16ip(parameter);
			stack.push(READ_LE_UINT32(localVars + parameter));
			debug(9, "CP_PUSH_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, READ_LE_UINT32(localVars + parameter));
			break;
		case CP_PUSH_GLOBAL_VAR32:
			// Push a global variable
			Read16ip(parameter);
			stack.push(readVar(parameter));
			debug(9, "CP_PUSH_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, readVar(parameter));
			break;
		case CP_PUSH_LOCAL_ADDR:
			// Push the address of a local variable

			// From what I understand, some scripts store data
			// (e.g. mouse pointers) in their local variable space
			// from the very beginning, and use this mechanism to
			// pass that data to the opcode function. I don't yet
			// know the conceptual difference between this and the
			// CP_PUSH_DEREFERENCED_STRUCTURE opcode.

			Read16ip(parameter);
			stack.push(_vm->_memory->encodePtr(localVars + parameter));
			debug(9, "CP_PUSH_LOCAL_ADDR: &localVars[%d] => %p", parameter / 4, localVars + parameter);
			break;
		case CP_PUSH_STRING:
			// Push the address of a string on to the stack
			// Get the string size
			Read8ip(parameter);

			// ip now points to the string
			ptr = code + ip;
			stack.push(_vm->_memory->encodePtr(ptr));
			debug(9, "CP_PUSH_STRING: \"%s\"", ptr);
			ip += (parameter + 1);
			break;
		case CP_PUSH_DEREFERENCED_STRUCTURE:
			// Push the address of a dereferenced structure
			Read32ip(parameter);
			ptr = objectData + 4 + ResHeader::size() + ObjectHub::size() + parameter;
			stack.push(_vm->_memory->encodePtr(ptr));
			debug(9, "CP_PUSH_DEREFERENCED_STRUCTURE: %d => %p", parameter, ptr);
			break;
		case CP_POP_LOCAL_VAR32:
			// Pop a value into a local word variable
			Read16ip(parameter);
			value = stack.pop();
			WRITE_LE_UINT32(localVars + parameter, value);
			debug(9, "CP_POP_LOCAL_VAR32: localVars[%d] = %d", parameter / 4, value);
			break;
		case CP_POP_GLOBAL_VAR32:
			// Pop a global variable
			Read16ip(parameter);
			value = stack.pop();

			// WORKAROUND for bug #1214168: The not-at-all dreaded
			// mop bug.
			//
			// At the London Docks, global variable 1003 keeps
			// track of Nico:
			//
			// 0: Hiding behind the first crate.
			// 1: Hiding behind the second crate.
			// 2: Standing in plain view on the deck.
			// 3: Hiding on the roof.
			//
			// The bug happens when trying to pick up the mop while
			// hiding on the roof. Nico climbs down, the mop is
			// picked up, but the variable remains set to 3.
			// Visually, everything looks ok. But as far as the
			// scripts are concerned, she's still hiding up on the
			// roof. This is not fatal, but leads to a number of
			// glitches until the state is corrected. E.g. trying
			// to climb back up the ladder will cause Nico to climb
			// down again.
			//
			// Global variable 1017 keeps track of the mop. Setting
			// it to 2 means that the mop has been picked up. We
			// use that as the signal that Nico's state needs to be
			// updated as well.

			if (checkMopBug && parameter == 1017 && readVar(1003) != 2) {
				warning("Working around mop bug: Setting Nico's state");
				writeVar(1003, 2);
			}

			writeVar(parameter, value);
			debug(9, "CP_POP_GLOBAL_VAR32: scriptsVars[%d] = %d", parameter, value);
			break;
		case CP_ADDNPOP_LOCAL_VAR32:
			Read16ip(parameter);
			value = READ_LE_UINT32(localVars + parameter) + stack.pop();
			WRITE_LE_UINT32(localVars + parameter, value);
			debug(9, "CP_ADDNPOP_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, value);
			break;
		case CP_SUBNPOP_LOCAL_VAR32:
			Read16ip(parameter);
			value = READ_LE_UINT32(localVars + parameter) - stack.pop();
			WRITE_LE_UINT32(localVars + parameter, value);
			debug(9, "CP_SUBNPOP_LOCAL_VAR32: localVars[%d] => %d", parameter / 4, value);
			break;
		case CP_ADDNPOP_GLOBAL_VAR32:
			// Add and pop a global variable
			Read16ip(parameter);
			value = readVar(parameter) + stack.pop();
			writeVar(parameter, value);
			debug(9, "CP_ADDNPOP_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, value);
			break;
		case CP_SUBNPOP_GLOBAL_VAR32:
			// Sub and pop a global variable
			Read16ip(parameter);
			value = readVar(parameter) - stack.pop();
			writeVar(parameter, value);
			debug(9, "CP_SUBNPOP_GLOBAL_VAR32: scriptVars[%d] => %d", parameter, value);
			break;

		// Jump opcodes

		case CP_SKIPONTRUE:
			// Skip if the value on the stack is true
			Read32ipLeaveip(parameter);
			value = stack.pop();
			if (!value) {
				ip += 4;
				debug(9, "CP_SKIPONTRUE: %d (IS FALSE (NOT SKIPPED))", parameter);
			} else {
				ip += parameter;
				debug(9, "CP_SKIPONTRUE: %d (IS TRUE (SKIPPED))", parameter);
			}
			break;
		case CP_SKIPONFALSE:
			// Skip if the value on the stack is false
			Read32ipLeaveip(parameter);
			value = stack.pop();
			if (value) {
				ip += 4;
				debug(9, "CP_SKIPONFALSE: %d (IS TRUE (NOT SKIPPED))", parameter);
			} else {
				ip += parameter;
				debug(9, "CP_SKIPONFALSE: %d (IS FALSE (SKIPPED))", parameter);
			}
			break;
		case CP_SKIPALWAYS:
			// skip a block
			Read32ipLeaveip(parameter);
			ip += parameter;
			debug(9, "CP_SKIPALWAYS: %d", parameter);
			break;
		case CP_SWITCH:
			// switch
			value = stack.pop();
			Read32ip(caseCount);

			// Search the cases
			foundCase = false;
			for (i = 0; i < caseCount && !foundCase; i++) {
				if (value == (int32)READ_LE_UINT32(code + ip)) {
					// We have found the case, so lets
					// jump to it
					foundCase = true;
					ip += READ_LE_UINT32(code + ip + 4);
				} else
					ip += 4 * 2;
			}

			// If we found no matching case then use the default

			if (!foundCase)
				ip += READ_LE_UINT32(code + ip);

			debug(9, "CP_SWITCH: [SORRY, NO DEBUG INFO]");
			break;
		case CP_SAVE_MCODE_START:
			// Save the start position on an mcode instruction in
			// case we need to restart it again
			savedStartOfMcode = ip - 1;
			debug(9, "CP_SAVE_MCODE_START");
			break;
		case CP_CALL_MCODE:
			// Call an mcode routine
			Read16ip(parameter);
			assert(parameter < _numOpcodes);
			// amount to adjust stack by (no of parameters)
			Read8ip(value);
			debug(9, "CP_CALL_MCODE: '%s', %d", _opcodes[parameter].desc, value);

			// The scripts do not always call the mcode command
			// with as many parameters as it can accept. To keep
			// things predictable, initialise the remaining
			// parameters to 0.

			for (i = STACK_SIZE - 1; i >= value; i--) {
				opcodeParams[i] = 0;
			}

			while (--value >= 0) {
				opcodeParams[value] = stack.pop();
			}

			retVal = (this->*_opcodes[parameter].proc)(opcodeParams);

			switch (retVal & 7) {
			case IR_STOP:
				// Quit out for a cycle
				WRITE_LE_UINT32(offsetPtr, ip);
				return 0;
			case IR_CONT:
				// Continue as normal
				break;
			case IR_TERMINATE:
				if (checkPearlBug && readVar(1290) == 0) {
					// Pearl's interaction script will wait
					// until global(1290) is no longer 0
					// before doing anything. But if the
					// script was terminated prematurely,
					// that never happens.
					warning("Working around Pearl bug: Resetting Pearl's state");
					writeVar(1290, 1);
				}
				// Return without updating the offset
				return 2;
			case IR_REPEAT:
				// Return setting offset to start of this
				// function call
				WRITE_LE_UINT32(offsetPtr, savedStartOfMcode);
				return 0;
			case IR_GOSUB:
				// that's really neat
				WRITE_LE_UINT32(offsetPtr, ip);
				return 2;
			default:
				error("Bad return code (%d) from '%s'", retVal & 7, _opcodes[parameter].desc);
			}
			parameterReturnedFromMcodeFunction = retVal >> 3;
			break;
		case CP_JUMP_ON_RETURNED:
			// Jump to a part of the script depending on
			// the return value from an mcode routine

			// Get the maximum value
			Read8ip(parameter);
			debug(9, "CP_JUMP_ON_RETURNED: %d => %d",
				parameterReturnedFromMcodeFunction,
				READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4));
			ip += READ_LE_UINT32(code + ip + parameterReturnedFromMcodeFunction * 4);
			break;

		// Operators

		case OP_ISEQUAL:
			b = stack.pop();
			a = stack.pop();
			stack.push(a == b);
			debug(9, "OP_ISEQUAL: RESULT = %d", a == b);
			break;
		case OP_NOTEQUAL:
			b = stack.pop();
			a = stack.pop();
			stack.push(a != b);
			debug(9, "OP_NOTEQUAL: RESULT = %d", a != b);
			break;
		case OP_GTTHAN:
			b = stack.pop();
			a = stack.pop();
			stack.push(a > b);
			debug(9, "OP_GTTHAN: RESULT = %d", a > b);
			break;
		case OP_LSTHAN:
			b = stack.pop();
			a = stack.pop();
			stack.push(a < b);
			debug(9, "OP_LSTHAN: RESULT = %d", a < b);
			break;
		case OP_GTTHANE:
			b = stack.pop();
			a = stack.pop();
			stack.push(a >= b);
			debug(9, "OP_GTTHANE: RESULT = %d", a >= b);
			break;
		case OP_LSTHANE:
			b = stack.pop();
			a = stack.pop();
			stack.push(a <= b);
			debug(9, "OP_LSTHANE: RESULT = %d", a <= b);
			break;
		case OP_PLUS:
			b = stack.pop();
			a = stack.pop();
			stack.push(a + b);
			debug(9, "OP_PLUS: RESULT = %d", a + b);
			break;
		case OP_MINUS:
			b = stack.pop();
			a = stack.pop();
			stack.push(a - b);
			debug(9, "OP_MINUS: RESULT = %d", a - b);
			break;
		case OP_TIMES:
			b = stack.pop();
			a = stack.pop();
			stack.push(a * b);
			debug(9, "OP_TIMES: RESULT = %d", a * b);
			break;
		case OP_DIVIDE:
			b = stack.pop();
			a = stack.pop();
			stack.push(a / b);
			debug(9, "OP_DIVIDE: RESULT = %d", a / b);
			break;
		case OP_ANDAND:
			b = stack.pop();
			a = stack.pop();
			stack.push(a && b);
			debug(9, "OP_ANDAND: RESULT = %d", a && b);
			break;
		case OP_OROR:
			b = stack.pop();
			a = stack.pop();
			stack.push(a || b);
			debug(9, "OP_OROR: RESULT = %d", a || b);
			break;

		// Debugging opcodes, I think

		case CP_DEBUGON:
			debug(9, "CP_DEBUGON");
			break;
		case CP_DEBUGOFF:
			debug(9, "CP_DEBUGOFF");
			break;
		case CP_TEMP_TEXT_PROCESS:
			Read32ip(parameter);
			debug(9, "CP_TEMP_TEXT_PROCESS: %d", parameter);
			break;
		default:
			error("Invalid script command %d", curCommand);
			return 3;	// for compilers that don't support NORETURN
		}
	}

	return 1;
}