Beispiel #1
0
bool GameFeatures::autoDetectSci21StringFunctionType() {
	// Look up the script address
	reg_t addr = getDetectionAddr("Str", SELECTOR(size));

	if (!addr.segment)
		return false;

	uint16 offset = addr.offset;
	Script *script = _segMan->getScript(addr.segment);

	while (true) {
		int16 opparams[4];
		byte extOpcode;
		byte opcode;
		offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
		opcode = extOpcode >> 1;

		// Check for end of script
		if (opcode == op_ret || offset >= script->getBufSize())
			break;

		if (opcode == op_callk) {
			uint16 kFuncNum = opparams[0];

			// SCI2.1 games which use the new kString functions call kString(8).
			// Earlier ones call the callKernel script function, but not kString
			// directly
			if (_kernel->getKernelName(kFuncNum) == "String")
				return true;
		}
	}

	return false;	// not found a call to kString
}
Beispiel #2
0
bool GameFeatures::autoDetectSci21KernelType() {
	// First, check if the Sound object is loaded
	reg_t soundObjAddr = _segMan->findObjectByName("Sound");
	if (soundObjAddr.isNull()) {
		// Usually, this means that the Sound object isn't loaded yet.
		// This case doesn't occur in early SCI2.1 games, and we've only
		// seen it happen in the RAMA demo, thus we can assume that the
		// game is using a SCI2.1 table
		warning("autoDetectSci21KernelType(): Sound object not loaded, assuming a SCI2.1 table");
		_sci21KernelType = SCI_VERSION_2_1;
		return true;
	}

	// Look up the script address
	reg_t addr = getDetectionAddr("Sound", SELECTOR(play));

	if (!addr.segment)
		return false;

	uint16 offset = addr.offset;
	Script *script = _segMan->getScript(addr.segment);

	while (true) {
		int16 opparams[4];
		byte extOpcode;
		byte opcode;
		offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
		opcode = extOpcode >> 1;

		// Check for end of script
		if (opcode == op_ret || offset >= script->getBufSize())
			break;

		if (opcode == op_callk) {
			uint16 kFuncNum = opparams[0];

			// Here we check for the kDoSound opcode that's used in SCI2.1.
			// Finding 0x40 as kDoSound in the Sound::play() function means the
			// game is using the modified SCI2 kernel table found in some older
			// SCI2.1 games (GK2 demo, KQ7 v1.4).
			// Finding 0x75 as kDoSound means the game is using the regular
			// SCI2.1 kernel table.
			if (kFuncNum == 0x40) {
				_sci21KernelType = SCI_VERSION_2;
				return true;
			} else if (kFuncNum == 0x75) {
				_sci21KernelType = SCI_VERSION_2_1;
				return true;
			}
		}
	}

	return false;	// not found
}
Beispiel #3
0
bool GameFeatures::autoDetectLofsType(Common::String gameSuperClassName, int methodNum) {
	// Look up the script address
	reg_t addr = getDetectionAddr(gameSuperClassName.c_str(), -1, methodNum);

	if (!addr.getSegment())
		return false;

	uint16 offset = addr.getOffset();
	Script *script = _segMan->getScript(addr.getSegment());

	while (true) {
		int16 opparams[4];
		byte extOpcode;
		byte opcode;
		offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
		opcode = extOpcode >> 1;

		// Check for end of script
		if (opcode == op_ret || offset >= script->getBufSize())
			break;

		if (opcode == op_lofsa || opcode == op_lofss) {
			// Load lofs operand
			uint16 lofs = opparams[0];

			// Check for going out of bounds when interpreting as abs/rel
			if (lofs >= script->getBufSize())
				_lofsType = SCI_VERSION_0_EARLY;

			if ((signed)offset + (int16)lofs < 0)
				_lofsType = SCI_VERSION_1_MIDDLE;

			if ((signed)offset + (int16)lofs >= (signed)script->getBufSize())
				_lofsType = SCI_VERSION_1_MIDDLE;

			if (_lofsType != SCI_VERSION_NONE)
				return true;

			// If we reach here, we haven't been able to deduce the lofs
			// parameter type so far.
		}
	}

	return false;	// not found
}
Beispiel #4
0
bool GameFeatures::autoDetectGfxFunctionsType(int methodNum) {
	// Look up the script address
	reg_t addr = getDetectionAddr("Rm", SELECTOR(overlay), methodNum);

	if (!addr.getSegment())
		return false;

	uint16 offset = addr.getOffset();
	Script *script = _segMan->getScript(addr.getSegment());

	while (true) {
		int16 opparams[4];
		byte extOpcode;
		byte opcode;
		offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
		opcode = extOpcode >> 1;

		// Check for end of script
		if (opcode == op_ret || offset >= script->getBufSize())
			break;

		if (opcode == op_callk) {
			uint16 kFuncNum = opparams[0];
			uint16 argc = opparams[1];

			if (kFuncNum == 8) {	// kDrawPic	(SCI0 - SCI11)
				// If kDrawPic is called with 6 parameters from the overlay
				// selector, the game is using old graphics functions.
				// Otherwise, if it's called with 8 parameters (e.g. SQ3) or 4 parameters
				// (e.g. Hoyle 1/2), it's using new graphics functions.
				_gfxFunctionsType = (argc == 6) ? SCI_VERSION_0_EARLY : SCI_VERSION_0_LATE;
				return true;
			}
		}
	}

	return false;	// not found
}
Beispiel #5
0
bool GameFeatures::autoDetectMoveCountType() {
	// Look up the script address
	reg_t addr = getDetectionAddr("Motion", SELECTOR(doit));

	if (!addr.getSegment())
		return false;

	uint16 offset = addr.getOffset();
	Script *script = _segMan->getScript(addr.getSegment());
	bool foundTarget = false;

	while (true) {
		int16 opparams[4];
		byte extOpcode;
		byte opcode;
		offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
		opcode = extOpcode >> 1;

		// Check for end of script
		if (opcode == op_ret || offset >= script->getBufSize())
			break;

		if (opcode == op_callk) {
			uint16 kFuncNum = opparams[0];

			// Games which ignore move count call kAbs before calling kDoBresen
			if (_kernel->getKernelName(kFuncNum) == "Abs") {
				foundTarget = true;
			} else if (_kernel->getKernelName(kFuncNum) == "DoBresen") {
				_moveCountType = foundTarget ? kIgnoreMoveCount : kIncrementMoveCount;
				return true;
			}
		}
	}

	return false;	// not found
}
Beispiel #6
0
bool GameFeatures::autoDetectSoundType() {
	// Look up the script address
	reg_t addr = getDetectionAddr("Sound", SELECTOR(play));

	if (!addr.getSegment())
		return false;

	uint16 offset = addr.getOffset();
	Script *script = _segMan->getScript(addr.getSegment());
	uint16 intParam = 0xFFFF;
	bool foundTarget = false;

	while (true) {
		int16 opparams[4];
		byte extOpcode;
		byte opcode;
		offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
		opcode = extOpcode >> 1;

		// Check for end of script
		if (opcode == op_ret || offset >= script->getBufSize())
			break;

		// The play method of the Sound object pushes the DoSound command that
		// it will use just before it calls DoSound. We intercept that here in
		// order to check what sound semantics are used, cause the position of
		// the sound commands has changed at some point during SCI1 middle.
		if (opcode == op_pushi) {
			// Load the pushi parameter
			intParam = opparams[0];
		} else if (opcode == op_callk) {
			uint16 kFuncNum = opparams[0];

			// Late SCI1 games call kIsObject before kDoSound
			if (kFuncNum == 6) {	// kIsObject (SCI0-SCI11)
				foundTarget = true;
			} else if (kFuncNum == 45) {	// kDoSound (SCI1)
				// First, check which DoSound function is called by the play
				// method of the Sound object
				switch (intParam) {
				case 1:
					_doSoundType = SCI_VERSION_0_EARLY;
					break;
				case 7:
					_doSoundType = SCI_VERSION_1_EARLY;
					break;
				case 8:
					_doSoundType = SCI_VERSION_1_LATE;
					break;
				default:
					// Unknown case... should never happen. We fall back to
					// alternative detection here, which works in general, apart
					// from some transitive games like Jones CD
					_doSoundType = foundTarget ? SCI_VERSION_1_LATE : SCI_VERSION_1_EARLY;
					break;
				}

				if (_doSoundType != SCI_VERSION_NONE)
					return true;
			}
		}
	}

	return false;	// not found
}
Beispiel #7
0
bool GameFeatures::autoDetectSci21KernelType() {
	// First, check if the Sound object is loaded
	reg_t soundObjAddr = _segMan->findObjectByName("Sound");
	if (soundObjAddr.isNull()) {
		// Usually, this means that the Sound object isn't loaded yet.
		// This case doesn't occur in early SCI2.1 games, and we've only
		// seen it happen in the RAMA demo, thus we can assume that the
		// game is using a SCI2.1 table

		// HACK: The Inside the Chest Demo and King's Questions minigame
		// don't have sounds at all, but they're using a SCI2 kernel
		if (g_sci->getGameId() == GID_CHEST || g_sci->getGameId() == GID_KQUESTIONS) {
			_sci21KernelType = SCI_VERSION_2;
			return true;
		}

		warning("autoDetectSci21KernelType(): Sound object not loaded, assuming a SCI2.1 table");
		_sci21KernelType = SCI_VERSION_2_1_EARLY;
		return true;
	}

	// Look up the script address
	reg_t addr = getDetectionAddr("Sound", SELECTOR(play));

	if (!addr.getSegment())
		return false;

	uint16 offset = addr.getOffset();
	Script *script = _segMan->getScript(addr.getSegment());

	while (true) {
		int16 opparams[4];
		byte extOpcode;
		byte opcode;
		offset += readPMachineInstruction(script->getBuf(offset), extOpcode, opparams);
		opcode = extOpcode >> 1;

		// Check for end of script
		// We don't check for op_ret here because the Phantasmagoria Mac script
		// has an op_ret early on in its script (controlled by a branch).
		if (offset >= script->getBufSize())
			break;

		if (opcode == op_callk) {
			uint16 kFuncNum = opparams[0];

			// Here we check for the kDoSound opcode that's used in SCI2.1.
			// Finding 0x40 as kDoSound in the Sound::play() function means the
			// game is using the modified SCI2 kernel table found in some older
			// SCI2.1 games (GK2 demo, KQ7 v1.4).
			// Finding 0x75 as kDoSound means the game is using the regular
			// SCI2.1 kernel table.
			if (kFuncNum == 0x40) {
				_sci21KernelType = SCI_VERSION_2;
				return true;
			} else if (kFuncNum == 0x75) {
				_sci21KernelType = SCI_VERSION_2_1_EARLY;
				return true;
			}
		}
	}

	return false;	// not found
}
Beispiel #8
0
// Disassembles one command from the heap, returns address of next command or 0 if a ret was encountered.
reg_t disassemble(EngineState *s, reg32_t pos, reg_t objAddr, bool printBWTag, bool printBytecode) {
	SegmentObj *mobj = s->_segMan->getSegment(pos.getSegment(), SEG_TYPE_SCRIPT);
	Script *script_entity = NULL;
	reg_t retval;
	retval.setSegment(pos.getSegment());
	retval.setOffset(pos.getOffset() + 1);
	uint16 param_value = 0xffff; // Suppress GCC warning by setting default value, chose value as invalid to getKernelName etc.
	uint i = 0;
	Kernel *kernel = g_sci->getKernel();

	if (!mobj) {
		warning("Disassembly failed: Segment %04x non-existent or not a script", pos.getSegment());
		return retval;
	} else
		script_entity = (Script *)mobj;

	uint scr_size = script_entity->getBufSize();

	if (pos.getOffset() >= scr_size) {
		warning("Trying to disassemble beyond end of script");
		return NULL_REG;
	}

	const byte *scr = script_entity->getBuf();

	int16 opparams[4];
	byte opsize;
	uint bytecount = readPMachineInstruction(scr + pos.getOffset(), opsize, opparams);
	const byte opcode = opsize >> 1;

	debugN("%04x:%04x: ", PRINT_REG(pos));

	if (printBytecode) {
		if (pos.getOffset() + bytecount > scr_size) {
			warning("Operation arguments extend beyond end of script");
			return retval;
		}

		for (i = 0; i < bytecount; i++)
			debugN("%02x ", scr[pos.getOffset() + i]);

		for (i = bytecount; i < 5; i++)
			debugN("   ");
	}

	opsize &= 1; // byte if true, word if false

	if (printBWTag)
		debugN("[%c] ", opsize ? 'B' : 'W');

	if (opcode == op_pushSelf && opsize && g_sci->getGameId() != GID_FANMADE) { // 0x3e (62)
		// Debug opcode op_file
		debugN("file \"%s\"\n", scr + pos.getOffset() + 1);	// +1: op_pushSelf size
		retval.incOffset(bytecount - 1);
		return retval;
	}

#ifndef REDUCE_MEMORY_USAGE
	debugN("%-5s", opcodeNames[opcode]);
#endif

	static const char *defaultSeparator = "\t\t; ";

	i = 0;
	while (g_sci->_opcode_formats[opcode][i]) {
		switch (g_sci->_opcode_formats[opcode][i++]) {
		case Script_Invalid:
			warning("-Invalid operation-");
			break;

		case Script_SByte:
		case Script_Byte:
			param_value = scr[retval.getOffset()];
			debugN("\t%02x", param_value);
			if (param_value > 9) {
				debugN("%s%u", defaultSeparator, param_value);
			}
			retval.incOffset(1);
			break;

		case Script_Word:
		case Script_SWord:
			param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
			debugN("\t%04x", param_value);
			if (param_value > 9) {
				debugN("%s%u", defaultSeparator, param_value);
			}

			retval.incOffset(2);
			break;

		case Script_SVariable:
		case Script_Variable:
		case Script_Property:
		case Script_Global:
		case Script_Local:
		case Script_Temp:
		case Script_Param:
			if (opsize) {
				param_value = scr[retval.getOffset()];
				retval.incOffset(1);
			} else {
				param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
				retval.incOffset(2);
			}

			if (opcode == op_callk) {
				debugN("\t%s[%x],", (param_value < kernel->_kernelFuncs.size()) ?
							((param_value < kernel->getKernelNamesSize()) ? kernel->getKernelName(param_value).c_str() : "[Unknown(postulated)]")
							: "<invalid>", param_value);
			} else if (opcode == op_class) {
				const reg_t classAddr = s->_segMan->getClassAddress(param_value, SCRIPT_GET_DONT_LOAD, retval.getSegment());
				if (!classAddr.isNull()) {
					debugN("\t%s", s->_segMan->getObjectName(classAddr));
					debugN(opsize ? "[%02x]" : "[%04x]", param_value);
				} else {
					debugN(opsize ? "\t%02x" : "\t%04x", param_value);
				}
			} else if (opcode == op_super) {
				Object *obj;
				if (objAddr != NULL_REG && (obj = s->_segMan->getObject(objAddr)) != nullptr) {
					debugN("\t%s", s->_segMan->getObjectName(obj->getSuperClassSelector()));
					debugN(opsize ? "[%02x]" : "[%04x]", param_value);
				} else {
					debugN(opsize ? "\t%02x" : "\t%04x", param_value);
				}

				debugN(",");
#ifdef ENABLE_SCI32
			} else if (getSciVersion() == SCI_VERSION_3 && (
				opcode == op_pToa || opcode == op_aTop ||
				opcode == op_pTos || opcode == op_sTop ||
				opcode == op_ipToa || opcode == op_dpToa ||
				opcode == op_ipTos || opcode == op_dpTos)) {

				const char *selectorName = "<invalid>";

				if (param_value < kernel->getSelectorNamesSize()) {
					selectorName = kernel->getSelectorName(param_value).c_str();
				}

				debugN("\t%s[%x]", selectorName, param_value);
#endif
			} else {
				const char *separator = defaultSeparator;

				debugN(opsize ? "\t%02x" : "\t%04x", param_value);
				if (param_value > 9) {
					debugN("%s%u", separator, param_value);
					separator = ", ";
				}

				if (param_value >= 0x20 && param_value <= 0x7e) {
					debugN("%s'%c'", separator, param_value);
					separator = ", ";
				}

				if (opcode == op_pushi && param_value < kernel->getSelectorNamesSize()) {
					debugN("%s%s", separator, kernel->getSelectorName(param_value).c_str());
				}
			}

			break;

		case Script_Offset: {
			assert(opcode == op_lofsa || opcode == op_lofss);

			if (opsize) {
				param_value = scr[retval.getOffset()];
				retval.incOffset(1);
			} else {
				param_value = READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
				retval.incOffset(2);
			}

			const uint32 offset = findOffset(param_value, script_entity, retval.getOffset());
			reg_t addr;
			addr.setSegment(retval.getSegment());
			addr.setOffset(offset);
			debugN("\t%s", s->_segMan->getObjectName(addr));
			debugN(opsize ? "[%02x]" : "[%04x]", offset);
			break;
		}

		case Script_SRelative:
			if (opsize) {
				int8 offset = (int8)scr[retval.getOffset()];
				retval.incOffset(1);
				debugN("\t%02x  [%04x]", 0xff & offset, kOffsetMask & (retval.getOffset() + offset));
			} else {
				int16 offset = (int16)READ_SCI11ENDIAN_UINT16(&scr[retval.getOffset()]);
				retval.incOffset(2);
				debugN("\t%04x  [%04x]", 0xffff & offset, kOffsetMask & (retval.getOffset() + offset));
			}
			break;

		case Script_End:
			retval = NULL_REG;
			break;

		default:
			error("Internal assertion failed in disassemble()");

		}
	}

	if (pos == s->xs->addr.pc) { // Extra information if debugging the current opcode
		if ((opcode == op_pTos) || (opcode == op_sTop) || (opcode == op_pToa) || (opcode == op_aTop) ||
		        (opcode == op_dpToa) || (opcode == op_ipToa) || (opcode == op_dpTos) || (opcode == op_ipTos)) {
			const Object *obj = s->_segMan->getObject(s->xs->objp);
			if (!obj) {
				warning("Attempted to reference on non-object at %04x:%04x", PRINT_REG(s->xs->objp));
			} else {
				if (getSciVersion() == SCI_VERSION_3)
					debugN("\t(%s)", g_sci->getKernel()->getSelectorName(param_value).c_str());
				else
					debugN("\t(%s)", g_sci->getKernel()->getSelectorName(obj->propertyOffsetToId(s->_segMan, param_value)).c_str());
			}
		}
	}

	debugN("\n");

	if (pos == s->xs->addr.pc) { // Extra information if debugging the current opcode
		if (opcode == op_callk) {
			int stackframe = (scr[pos.getOffset() + 2] >> 1) + (s->r_rest);
			int argc = ((s->xs->sp)[- stackframe - 1]).getOffset();
			bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);

			if (!oldScriptHeader)
				argc += (s->r_rest);

			debugN(" Kernel params: (");

			for (int j = 0; j < argc; j++) {
				debugN("%04x:%04x", PRINT_REG((s->xs->sp)[j - stackframe]));
				if (j + 1 < argc)
					debugN(", ");
			}
			debugN(")\n");
		} else if ((opcode == op_send) || (opcode == op_self)) {