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 }
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 }
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 }
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 }
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 }
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 }
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 }
// 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)) {