diff options
author | md5 | 2011-02-17 17:33:36 +0200 |
---|---|---|
committer | md5 | 2011-02-19 20:22:43 +0200 |
commit | 4e88d75eb6db2f7bb88d41ee4b8598f0e7f93bd9 (patch) | |
tree | e2ed80508be788f4d9b92f37836c55662031bde5 | |
parent | d7422de7f78e6d4d88a32cad3994a5910e2cced1 (diff) | |
download | scummvm-rg350-4e88d75eb6db2f7bb88d41ee4b8598f0e7f93bd9.tar.gz scummvm-rg350-4e88d75eb6db2f7bb88d41ee4b8598f0e7f93bd9.tar.bz2 scummvm-rg350-4e88d75eb6db2f7bb88d41ee4b8598f0e7f93bd9.zip |
SCI: Refactored the reg_t related operations and comparisons
This refactoring reduces a lot of code duplication, allows for better control,
makes the code more readable and allows us to remove a lot of now unneeded
workarounds
-rw-r--r-- | engines/sci/engine/vm.cpp | 686 | ||||
-rw-r--r-- | engines/sci/engine/vm_types.cpp | 247 | ||||
-rw-r--r-- | engines/sci/engine/vm_types.h | 97 | ||||
-rw-r--r-- | engines/sci/engine/workarounds.cpp | 76 | ||||
-rw-r--r-- | engines/sci/engine/workarounds.h | 11 | ||||
-rw-r--r-- | engines/sci/module.mk | 1 |
6 files changed, 451 insertions, 667 deletions
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index 9c7f52d28e..4f5a263331 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -163,18 +163,6 @@ static bool validate_variable(reg_t *r, reg_t *stack_base, int type, int max, in extern const char *opcodeNames[]; // from scriptdebug.cpp -static reg_t arithmetic_lookForWorkaround(const byte opcode, const SciWorkaroundEntry *workaroundList, reg_t value1, reg_t value2) { - SciTrackOriginReply originReply; - SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, workaroundList, &originReply); - if (solution.type == WORKAROUND_NONE) - error("%s on non-integer (%04x:%04x, %04x:%04x) from method %s::%s (script %d, room %d, localCall %x)", - opcodeNames[opcode], PRINT_REG(value1), PRINT_REG(value2), originReply.objectName.c_str(), - originReply.methodName.c_str(), originReply.scriptNr, g_sci->getEngineState()->currentRoomNumber(), - originReply.localCallOffset); - assert(solution.type == WORKAROUND_FAKE); - return make_reg(0, solution.value); -} - static reg_t validate_read_var(reg_t *r, reg_t *stack_base, int type, int max, int index, reg_t default_value) { if (validate_variable(r, stack_base, type, max, index)) { if (r[index].segment == 0xffff) { @@ -486,7 +474,7 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt if (argNr == 1) debugN(" - "); reg_t curParam = argp[argNr]; - if (curParam.segment) { + if (curParam.isPointer()) { debugN("[%04x:%04x] ", PRINT_REG(curParam)); displaySize += 12; } else { @@ -597,32 +585,6 @@ static ExecStack *add_exec_stack_entry(Common::List<ExecStack> &execStack, reg_t return &(execStack.back()); } -static reg_t pointer_add(EngineState *s, reg_t base, int offset) { - SegmentObj *mobj = s->_segMan->getSegmentObj(base.segment); - - if (!mobj) { - error("[VM] Error: Attempt to add %d to invalid pointer %04x:%04x", offset, PRINT_REG(base)); - return NULL_REG; - } - - switch (mobj->getType()) { - - case SEG_TYPE_LOCALS: - case SEG_TYPE_SCRIPT: - case SEG_TYPE_STACK: - case SEG_TYPE_DYNMEM: - base.offset += offset; - return base; - default: - // FIXME: Changed this to warning, because iceman does this during dancing with girl. - // Investigate why that is so and either fix the underlying issue or implement a more - // specialized workaround! - warning("[VM] Error: Attempt to add %d to pointer %04x:%04x, type %d: Pointer arithmetics of this type unsupported", offset, PRINT_REG(base), mobj->getType()); - return NULL_REG; - - } -} - static void addKernelCallToExecStack(EngineState *s, int kernelCallNr, int argc, reg_t *argv) { // Add stack frame to indicate we're executing a callk. // This is useful in debugger backtraces if this @@ -683,7 +645,7 @@ static void logKernelCall(const KernelFunction *kernelCall, const KernelSubFunct } } } - if (result.segment) + if (result.isPointer()) debugN(" = %04x:%04x\n", PRINT_REG(result)); else debugN(" = %d\n", result.offset); @@ -738,15 +700,15 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { } else { // Sub-functions available, check signature and call that one directly if (argc < 1) - error("[VM] k%s[%x]: no subfunction-id parameter given", kernelCall.name, kernelCallNr); - if (argv[0].segment) - error("[VM] k%s[%x]: given subfunction-id is actually a pointer", kernelCall.name, kernelCallNr); + error("[VM] k%s[%x]: no subfunction ID parameter given", kernelCall.name, kernelCallNr); + if (argv[0].isPointer()) + error("[VM] k%s[%x]: given subfunction ID is actually a pointer", kernelCall.name, kernelCallNr); const uint16 subId = argv[0].toUint16(); // Skip over subfunction-id argc--; argv++; if (subId >= kernelCall.subFunctionCount) - error("[VM] k%s: subfunction-id %d requested, but not available", kernelCall.name, subId); + error("[VM] k%s: subfunction ID %d requested, but not available", kernelCall.name, subId); const KernelSubFunction &kernelSubCall = kernelCall.subFunctions[subId]; if (kernelSubCall.signature && !kernel->signatureMatch(kernelSubCall.signature, argc, argv)) { // Signature mismatch @@ -779,7 +741,7 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { } } if (!kernelSubCall.function) - error("[VM] k%s: subfunction-id %d requested, but not available", kernelCall.name, subId); + error("[VM] k%s: subfunction ID %d requested, but not available", kernelCall.name, subId); addKernelCallToExecStack(s, kernelCallNr, argc, argv); s->r_acc = kernelSubCall.function(s, argc, argv); @@ -980,114 +942,38 @@ void run_vm(EngineState *s) { byte extOpcode; s->xs->addr.pc.offset += readPMachineInstruction(scr->getBuf() + s->xs->addr.pc.offset, extOpcode, opparams); const byte opcode = extOpcode >> 1; + //debug("%s", opcodeNames[opcode]); switch (opcode) { - case op_bnot: { // 0x00 (00) + case op_bnot: // 0x00 (00) // Binary not - int16 value = s->r_acc.toSint16(); - if (s->r_acc.isNumber()) - s->r_acc = make_reg(0, 0xffff ^ value); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, s->r_acc, NULL_REG); + s->r_acc = make_reg(0, 0xffff ^ s->r_acc.requireUint16()); break; - } case op_add: // 0x01 (01) - r_temp = POP32(); - - // Happens in SQ1, room 28, when throwing the water at Orat - if (s->r_acc.segment == 0xFFFF) { - // WORKAROUND: init uninitialized variable to 0 - warning("op_add: attempt to write to uninitialized variable"); - s->r_acc = NULL_REG; - } - - if (r_temp.segment || s->r_acc.segment) { - reg_t r_ptr = NULL_REG; - int offset; - // Pointer arithmetics! - if (s->r_acc.segment) { - if (r_temp.segment) { - error("Attempt to add two pointers, stack=%04x:%04x and acc=%04x:%04x", - PRINT_REG(r_temp), PRINT_REG(s->r_acc)); - offset = 0; - } else { - r_ptr = s->r_acc; - offset = r_temp.offset; - } - } else { - r_ptr = r_temp; - offset = s->r_acc.offset; - } - - s->r_acc = pointer_add(s, r_ptr, offset); - - } else - s->r_acc = make_reg(0, r_temp.offset + s->r_acc.offset); + s->r_acc = POP32() + s->r_acc; break; case op_sub: // 0x02 (02) - r_temp = POP32(); - if (r_temp.segment != s->r_acc.segment) { - reg_t r_ptr = NULL_REG; - int offset; - // Pointer arithmetics! - if (s->r_acc.segment) { - if (r_temp.segment) { - error("Attempt to subtract two pointers, stack=%04x:%04x and acc=%04x:%04x", - PRINT_REG(r_temp), PRINT_REG(s->r_acc)); - offset = 0; - } else { - r_ptr = s->r_acc; - offset = r_temp.offset; - } - } else { - r_ptr = r_temp; - offset = s->r_acc.offset; - } - - s->r_acc = pointer_add(s, r_ptr, -offset); - - } else { - // We can subtract numbers, or pointers with the same segment, - // an operation which will yield a number like in C - s->r_acc = make_reg(0, r_temp.offset - s->r_acc.offset); - } + s->r_acc = POP32() - s->r_acc; break; - case op_mul: { // 0x03 (03) - r_temp = POP32(); - int16 value1 = s->r_acc.toSint16(); - int16 value2 = r_temp.toSint16(); - if (s->r_acc.isNumber() && r_temp.isNumber()) - s->r_acc = make_reg(0, value1 * value2); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeMulWorkarounds, s->r_acc, r_temp); + case op_mul: // 0x03 (03) + s->r_acc = POP32() * s->r_acc; break; - } - case op_div: { // 0x04 (04) - r_temp = POP32(); - int16 divisor = s->r_acc.toSint16(); - int16 dividend = r_temp.toSint16(); - if (s->r_acc.isNumber() && r_temp.isNumber()) - s->r_acc = make_reg(0, (divisor != 0 ? dividend / divisor : 0)); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeDivWorkarounds, s->r_acc, r_temp); + case op_div: // 0x04 (04) + // we check for division by 0 inside the custom reg_t division operator + s->r_acc = POP32() / s->r_acc; break; - } case op_mod: { // 0x05 (05) - r_temp = POP32(); - if (getSciVersion() <= SCI_VERSION_0_LATE) { - uint16 modulo = s->r_acc.toUint16(); - uint16 value = r_temp.toUint16(); - if (s->r_acc.isNumber() && r_temp.isNumber()) - s->r_acc = make_reg(0, (modulo != 0 ? value % modulo : 0)); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, s->r_acc, r_temp); + uint16 modulo = s->r_acc.requireUint16(); + uint16 value = POP32().requireUint16(); + uint16 result = (modulo != 0 ? value % modulo : 0); + s->r_acc = make_reg(0, result); } else { // In Iceman (and perhaps from SCI0 0.000.685 onwards in general), // handling for negative numbers was added. Since Iceman doesn't @@ -1095,86 +981,41 @@ void run_vm(EngineState *s) { // for simplicity's sake and use the new code for SCI01 and newer // games. Fixes the battlecruiser mini game in SQ5 (room 850), // bug #3035755 - int16 modulo = s->r_acc.toSint16(); - int16 value = r_temp.toSint16(); - int16 result; - if (s->r_acc.isNumber() && r_temp.isNumber()) { - modulo = ABS(modulo); - result = (modulo != 0 ? value % modulo : 0); - if (result < 0) - result += modulo; - s->r_acc = make_reg(0, result); - } else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, s->r_acc, r_temp); + int16 modulo = ABS(s->r_acc.requireSint16()); + int16 value = POP32().requireSint16(); + int16 result = (modulo != 0 ? value % modulo : 0); + if (result < 0) + result += modulo; + s->r_acc = make_reg(0, result); } break; } - case op_shr: { // 0x06 (06) + case op_shr: // 0x06 (06) // Shift right logical - r_temp = POP32(); - uint16 value = r_temp.toUint16(); - uint16 shiftCount = s->r_acc.toUint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, value >> shiftCount); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); + s->r_acc = POP32() >> s->r_acc; break; - } - case op_shl: { // 0x07 (07) + case op_shl: // 0x07 (07) // Shift left logical - r_temp = POP32(); - uint16 value = r_temp.toUint16(); - uint16 shiftCount = s->r_acc.toUint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, value << shiftCount); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); + s->r_acc = POP32() << s->r_acc; break; - } - case op_xor: { // 0x08 (08) - r_temp = POP32(); - uint16 value1 = r_temp.toUint16(); - uint16 value2 = s->r_acc.toUint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, value1 ^ value2); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); + case op_xor: // 0x08 (08) + s->r_acc = POP32() ^ s->r_acc; break; - } - case op_and: { // 0x09 (09) - r_temp = POP32(); - uint16 value1 = r_temp.toUint16(); - uint16 value2 = s->r_acc.toUint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, value1 & value2); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeAndWorkarounds, r_temp, s->r_acc); + case op_and: // 0x09 (09) + s->r_acc = POP32() & s->r_acc; break; - } - case op_or: { // 0x0a (10) - r_temp = POP32(); - uint16 value1 = r_temp.toUint16(); - uint16 value2 = s->r_acc.toUint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, value1 | value2); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeOrWorkarounds, r_temp, s->r_acc); + case op_or: // 0x0a (10) + s->r_acc = POP32() | s->r_acc; break; - } - case op_neg: { // 0x0b (11) - int16 value = s->r_acc.toSint16(); - if (s->r_acc.isNumber()) - s->r_acc = make_reg(0, -value); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, s->r_acc, NULL_REG); + case op_neg: // 0x0b (11) + s->r_acc = make_reg(0, -s->r_acc.requireSint16()); break; - } case op_not: // 0x0c (12) s->r_acc = make_reg(0, !(s->r_acc.offset || s->r_acc.segment)); @@ -1182,194 +1023,57 @@ void run_vm(EngineState *s) { break; case op_eq_: // 0x0d (13) - // == s->r_prev = s->r_acc; - r_temp = POP32(); - s->r_acc = make_reg(0, r_temp == s->r_acc); - // Explicitly allow pointers to be compared + s->r_acc = make_reg(0, POP32() == s->r_acc); break; case op_ne_: // 0x0e (14) - // != s->r_prev = s->r_acc; - r_temp = POP32(); - s->r_acc = make_reg(0, r_temp != s->r_acc); - // Explicitly allow pointers to be compared + s->r_acc = make_reg(0, POP32() != s->r_acc); break; case op_gt_: // 0x0f (15) - // > s->r_prev = s->r_acc; - r_temp = POP32(); - if (r_temp.segment && s->r_acc.segment) { - // Signed pointer comparison. We do unsigned comparison instead, as that is probably what was intended. - if (r_temp.segment != s->r_acc.segment) - warning("[VM] Comparing pointers in different segments (%04x:%04x vs. %04x:%04x)", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); - s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset > s->r_acc.offset); - } else if (r_temp.segment && !s->r_acc.segment) { - if (s->r_acc.offset >= 1000) - error("[VM] op_gt: comparison between a pointer and number"); - // Pseudo-WORKAROUND: Sierra allows any pointer <-> value comparison - // Happens in SQ1, room 28, when throwing the water at Orat - s->r_acc = make_reg(0, 1); - } else { - int16 compare1 = r_temp.toSint16(); - int16 compare2 = s->r_acc.toSint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, compare1 > compare2); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); - } + s->r_acc = make_reg(0, POP32() > s->r_acc); break; case op_ge_: // 0x10 (16) - // >= s->r_prev = s->r_acc; - r_temp = POP32(); - if (r_temp.segment && s->r_acc.segment) { - if (r_temp.segment != s->r_acc.segment) - warning("[VM] Comparing pointers in different segments (%04x:%04x vs. %04x:%04x)", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); - s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset >= s->r_acc.offset); - } else { - int16 compare1 = r_temp.toSint16(); - int16 compare2 = s->r_acc.toSint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, compare1 >= compare2); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeGeWorkarounds, r_temp, s->r_acc); - } + s->r_acc = make_reg(0, POP32() >= s->r_acc); break; case op_lt_: // 0x11 (17) - // < s->r_prev = s->r_acc; - r_temp = POP32(); - if (r_temp.segment && s->r_acc.segment) { - if (r_temp.segment != s->r_acc.segment) - warning("[VM] Comparing pointers in different segments (%04x:%04x vs. %04x:%04x)", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); - s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset < s->r_acc.offset); - } else if (r_temp.segment && !s->r_acc.segment) { - if (s->r_acc.offset >= 1000) - error("[VM] op_lt: comparison between a pointer and number"); - // Pseudo-WORKAROUND: Sierra allows any pointer <-> value comparison - // Happens in SQ1, room 58, when giving id-card to robot - s->r_acc = make_reg(0, 1); - } else { - int16 compare1 = r_temp.toSint16(); - int16 compare2 = s->r_acc.toSint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, compare1 < compare2); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); - } + s->r_acc = make_reg(0, POP32() < s->r_acc); break; case op_le_: // 0x12 (18) - // <= s->r_prev = s->r_acc; - r_temp = POP32(); - if (r_temp.segment && s->r_acc.segment) { - if (r_temp.segment != s->r_acc.segment) - warning("[VM] Comparing pointers in different segments (%04x:%04x vs. %04x:%04x)", PRINT_REG(r_temp), PRINT_REG(s->r_acc)); - s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset <= s->r_acc.offset); - } else { - int16 compare1 = r_temp.toSint16(); - int16 compare2 = s->r_acc.toSint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, compare1 <= compare2); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeLeWorkarounds, r_temp, s->r_acc); - } + s->r_acc = make_reg(0, POP32() <= s->r_acc); break; case op_ugt_: // 0x13 (19) // > (unsigned) s->r_prev = s->r_acc; - r_temp = POP32(); - - // SCI0/SCI1 scripts use this to check whether a - // parameter is a pointer or a far text - // reference. It is used e.g. by the standard library - // Print function to distinguish two ways of calling it: - // - // (Print "foo") // Pointer to a string - // (Print 420 5) // Reference to the fifth message in text resource 420 - - // It works because in those games, the maximum resource number is 999, - // so any parameter value above that threshold must be a pointer. - if (r_temp.segment && (s->r_acc == make_reg(0, 1000))) - s->r_acc = make_reg(0, 1); - else if (r_temp.segment && s->r_acc.segment) - s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset > s->r_acc.offset); - else { - uint16 compare1 = r_temp.toUint16(); - uint16 compare2 = s->r_acc.toUint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, compare1 > compare2); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); - } + s->r_acc = make_reg(0, POP32().gtU(s->r_acc)); break; case op_uge_: // 0x14 (20) // >= (unsigned) s->r_prev = s->r_acc; - r_temp = POP32(); - - // See above - if (r_temp.segment && (s->r_acc == make_reg(0, 1000))) - s->r_acc = make_reg(0, 1); - else if (r_temp.segment && s->r_acc.segment) - s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset >= s->r_acc.offset); - else { - uint16 compare1 = r_temp.toUint16(); - uint16 compare2 = s->r_acc.toUint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, compare1 >= compare2); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); - } + s->r_acc = make_reg(0, POP32().geU(s->r_acc)); break; case op_ult_: // 0x15 (21) // < (unsigned) s->r_prev = s->r_acc; - r_temp = POP32(); - - // See above - // PQ2 japanese compares pointers to 2000 to find out if its a pointer or a resourceid - if (r_temp.segment && (s->r_acc == make_reg(0, 1000) || (s->r_acc == make_reg(0, 2000)))) - s->r_acc = NULL_REG; - else if (r_temp.segment && s->r_acc.segment) - s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset < s->r_acc.offset); - else { - uint16 compare1 = r_temp.toUint16(); - uint16 compare2 = s->r_acc.toUint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, compare1 < compare2); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeUltWorkarounds, r_temp, s->r_acc); - } + s->r_acc = make_reg(0, POP32().ltU(s->r_acc)); break; case op_ule_: // 0x16 (22) // <= (unsigned) s->r_prev = s->r_acc; - r_temp = POP32(); - - // See above - if (r_temp.segment && (s->r_acc == make_reg(0, 1000))) - s->r_acc = NULL_REG; - else if (r_temp.segment && s->r_acc.segment) - s->r_acc = make_reg(0, (r_temp.segment == s->r_acc.segment) && r_temp.offset <= s->r_acc.offset); - else { - uint16 compare1 = r_temp.toUint16(); - uint16 compare2 = s->r_acc.toUint16(); - if (r_temp.isNumber() && s->r_acc.isNumber()) - s->r_acc = make_reg(0, compare1 <= compare2); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, r_temp, s->r_acc); - } + s->r_acc = make_reg(0, POP32().leU(s->r_acc)); break; case op_bt: // 0x17 (23) @@ -1613,7 +1317,7 @@ void run_vm(EngineState *s) { // Send to any class r_temp = s->_segMan->getClassAddress(opparams[0], SCRIPT_GET_LOAD, s->xs->addr.pc); - if (!r_temp.segment) + if (!r_temp.isPointer()) error("[VM]: Invalid superclass in object"); else { s_temp = s->xs->sp; @@ -1696,87 +1400,29 @@ void run_vm(EngineState *s) { validate_property(s, obj, opparams[0]) = POP32(); break; - case op_ipToa: { // 0x35 (53) - // Increment Property and copy To Accumulator - reg_t &opProperty = validate_property(s, obj, opparams[0]); - uint16 valueProperty = opProperty.toUint16(); - if (opProperty.isNumber()) - s->r_acc = make_reg(0, valueProperty + 1); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, NULL, opProperty, NULL_REG); - opProperty = s->r_acc; - break; - } - - case op_dpToa: { // 0x36 (54) - // Decrement Property and copy To Accumulator - reg_t &opProperty = validate_property(s, obj, opparams[0]); - uint16 valueProperty = opProperty.toUint16(); - if (opProperty.isNumber()) - s->r_acc = make_reg(0, valueProperty - 1); - else - s->r_acc = arithmetic_lookForWorkaround(opcode, opcodeDptoaWorkarounds, opProperty, NULL_REG); - opProperty = s->r_acc; - break; - } - - case op_ipTos: { // 0x37 (55) - // Increment Property and push to Stack + case op_ipToa: // 0x35 (53) + case op_dpToa: // 0x36 (54) + case op_ipTos: // 0x37 (55) + case op_dpTos: // 0x38 (56) + { + // Increment/decrement a property and copy to accumulator, + // or push to stack reg_t &opProperty = validate_property(s, obj, opparams[0]); - uint16 valueProperty = opProperty.toUint16(); - if (opProperty.isNumber()) - valueProperty++; + if (opcode & 1) + opProperty += 1; else - valueProperty = arithmetic_lookForWorkaround(opcode, NULL, opProperty, NULL_REG).offset; - opProperty = make_reg(0, valueProperty); - PUSH(valueProperty); - break; - } + opProperty -= 1; - case op_dpTos: { // 0x38 (56) - // Decrement Property and push to Stack - reg_t &opProperty = validate_property(s, obj, opparams[0]); - uint16 valueProperty = opProperty.toUint16(); - if (opProperty.isNumber()) - valueProperty--; + if (opcode == op_ipToa || opcode == op_dpToa) + s->r_acc = opProperty; else - valueProperty = arithmetic_lookForWorkaround(opcode, NULL, opProperty, NULL_REG).offset; - opProperty = make_reg(0, valueProperty); - PUSH(valueProperty); + PUSH32(opProperty); break; } case op_lofsa: // 0x39 (57) - // Load Offset to Accumulator - s->r_acc.segment = s->xs->addr.pc.segment; - - switch (g_sci->_features->detectLofsType()) { - case SCI_VERSION_0_EARLY: - s->r_acc.offset = s->xs->addr.pc.offset + opparams[0]; - break; - case SCI_VERSION_1_MIDDLE: - s->r_acc.offset = opparams[0]; - break; - case SCI_VERSION_1_1: - s->r_acc.offset = opparams[0] + local_script->getScriptSize(); - break; - case SCI_VERSION_3: - // In theory this can break if the variant with a one-byte argument is - // used. For now, assume it doesn't happen. - s->r_acc.offset = local_script->relocateOffsetSci3(s->xs->addr.pc.offset-2); - break; - default: - error("Unknown lofs type"); - } - - if (s->r_acc.offset >= scr->getBufSize()) { - error("VM: lofsa operation overflowed: %04x:%04x beyond end" - " of script (at %04x)", PRINT_REG(s->r_acc), scr->getBufSize()); - } - break; - case op_lofss: // 0x3a (58) - // Load Offset to Stack + // Load offset to accumulator or push to stack r_temp.segment = s->xs->addr.pc.segment; switch (g_sci->_features->detectLofsType()) { @@ -1790,17 +1436,22 @@ void run_vm(EngineState *s) { r_temp.offset = opparams[0] + local_script->getScriptSize(); break; case SCI_VERSION_3: - r_temp.offset = opparams[0]; + // In theory this can break if the variant with a one-byte argument is + // used. For now, assume it doesn't happen. + r_temp.offset = local_script->relocateOffsetSci3(s->xs->addr.pc.offset-2); break; default: error("Unknown lofs type"); } - if (r_temp.offset >= scr->getBufSize()) { - error("VM: lofss operation overflowed: %04x:%04x beyond end" + if (r_temp.offset >= scr->getBufSize()) + error("VM: lofsa/lofss operation overflowed: %04x:%04x beyond end" " of script (at %04x)", PRINT_REG(r_temp), scr->getBufSize()); - } - PUSH32(r_temp); + + if (opcode == op_lofsa) + s->r_acc = r_temp; + else + PUSH32(r_temp); break; case op_push0: // 0x3b (59) @@ -1838,8 +1489,14 @@ void run_vm(EngineState *s) { case op_lat: // 0x42 (66) case op_lap: // 0x43 (67) // Load global, local, temp or param variable into the accumulator + case op_lagi: // 0x48 (72) + case op_lali: // 0x49 (73) + case op_lati: // 0x4a (74) + case op_lapi: // 0x4b (75) + // Same as the 4 ones above, except that the accumulator is used as + // an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0]; + var_number = opparams[0] + (opcode >= op_lagi ? s->r_acc.requireSint16() : 0); s->r_acc = READ_VAR(var_type, var_number); break; @@ -1848,85 +1505,48 @@ void run_vm(EngineState *s) { case op_lst: // 0x46 (70) case op_lsp: // 0x47 (71) // Load global, local, temp or param variable into the stack - var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0]; - PUSH32(READ_VAR(var_type, var_number)); - break; - - case op_lagi: // 0x48 (72) - case op_lali: // 0x49 (73) - case op_lati: // 0x4a (74) - case op_lapi: { // 0x4b (75) - // Load global, local, temp or param variable into the accumulator, - // using the accumulator as an additional index - var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - int16 value = s->r_acc.toSint16(); - if (!s->r_acc.isNumber()) - value = arithmetic_lookForWorkaround(opcode, opcodeLaiWorkarounds, s->r_acc, NULL_REG).offset; - var_number = opparams[0] + value; - s->r_acc = READ_VAR(var_type, var_number); - break; - } - case op_lsgi: // 0x4c (76) case op_lsli: // 0x4d (77) case op_lsti: // 0x4e (78) - case op_lspi: { // 0x4f (79) - // Load global, local, temp or param variable into the stack, - // using the accumulator as an additional index + case op_lspi: // 0x4f (79) + // Same as the 4 ones above, except that the accumulator is used as + // an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - int16 value = s->r_acc.toSint16(); - if (!s->r_acc.isNumber()) - value = arithmetic_lookForWorkaround(opcode, opcodeLsiWorkarounds, s->r_acc, NULL_REG).offset; - var_number = opparams[0] + value; + var_number = opparams[0] + (opcode >= op_lsgi ? s->r_acc.requireSint16() : 0); PUSH32(READ_VAR(var_type, var_number)); break; - } case op_sag: // 0x50 (80) case op_sal: // 0x51 (81) case op_sat: // 0x52 (82) case op_sap: // 0x53 (83) // Save the accumulator into the global, local, temp or param variable - var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0]; - WRITE_VAR(var_type, var_number, s->r_acc); - break; - - case op_ssg: // 0x54 (84) - case op_ssl: // 0x55 (85) - case op_sst: // 0x56 (86) - case op_ssp: // 0x57 (87) - // Save the stack into the global, local, temp or param variable - var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0]; - WRITE_VAR(var_type, var_number, POP32()); - break; - case op_sagi: // 0x58 (88) case op_sali: // 0x59 (89) case op_sati: // 0x5a (90) case op_sapi: // 0x5b (91) // Save the accumulator into the global, local, temp or param variable, // using the accumulator as an additional index - - // Special semantics because it wouldn't really make a whole lot - // of sense otherwise, with acc being used for two things - // simultaneously... var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0] + s->r_acc.requireSint16(); - s->r_acc = POP32(); + var_number = opparams[0] + (opcode >= op_sagi ? s->r_acc.requireSint16() : 0); + if (opcode >= op_sagi) // load the actual value to store in the accumulator + s->r_acc = POP32(); WRITE_VAR(var_type, var_number, s->r_acc); break; + case op_ssg: // 0x54 (84) + case op_ssl: // 0x55 (85) + case op_sst: // 0x56 (86) + case op_ssp: // 0x57 (87) + // Save the stack into the global, local, temp or param variable case op_ssgi: // 0x5c (92) case op_ssli: // 0x5d (93) case op_ssti: // 0x5e (94) case op_sspi: // 0x5f (95) - // Save the stack into the global, local, temp or param variable, - // using the accumulator as an additional index + // Same as the 4 ones above, except that the accumulator is used as + // an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0] + s->r_acc.requireSint16(); + var_number = opparams[0] + (opcode >= op_ssgi ? s->r_acc.requireSint16() : 0); WRITE_VAR(var_type, var_number, POP32()); break; @@ -1936,14 +1556,15 @@ void run_vm(EngineState *s) { case op_plusap: // 0x63 (99) // Increment the global, local, temp or param variable and save it // to the accumulator + case op_plusagi: // 0x68 (104) + case op_plusali: // 0x69 (105) + case op_plusati: // 0x6a (106) + case op_plusapi: // 0x6b (107) + // Same as the 4 ones above, except that the accumulator is used as + // an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0]; - r_temp = READ_VAR(var_type, var_number); - if (r_temp.segment) { - // Pointer arithmetics! - s->r_acc = pointer_add(s, r_temp, 1); - } else - s->r_acc = make_reg(0, r_temp.offset + 1); + var_number = opparams[0] + (opcode >= op_plusagi ? s->r_acc.requireSint16() : 0); + s->r_acc = READ_VAR(var_type, var_number) + 1; WRITE_VAR(var_type, var_number, s->r_acc); break; @@ -1953,49 +1574,15 @@ void run_vm(EngineState *s) { case op_plussp: // 0x67 (103) // Increment the global, local, temp or param variable and save it // to the stack - var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0]; - r_temp = READ_VAR(var_type, var_number); - if (r_temp.segment) { - // Pointer arithmetics! - r_temp = pointer_add(s, r_temp, 1); - } else - r_temp = make_reg(0, r_temp.offset + 1); - PUSH32(r_temp); - WRITE_VAR(var_type, var_number, r_temp); - break; - - case op_plusagi: // 0x68 (104) - case op_plusali: // 0x69 (105) - case op_plusati: // 0x6a (106) - case op_plusapi: // 0x6b (107) - // Increment the global, local, temp or param variable and save it - // to the accumulator, using the accumulator as an additional index - var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0] + s->r_acc.requireSint16(); - r_temp = READ_VAR(var_type, var_number); - if (r_temp.segment) { - // Pointer arithmetics! - s->r_acc = pointer_add(s, r_temp, 1); - } else - s->r_acc = make_reg(0, r_temp.offset + 1); - WRITE_VAR(var_type, var_number, s->r_acc); - break; - case op_plussgi: // 0x6c (108) case op_plussli: // 0x6d (109) case op_plussti: // 0x6e (110) case op_plusspi: // 0x6f (111) - // Increment the global, local, temp or param variable and save it - // to the stack, using the accumulator as an additional index + // Same as the 4 ones above, except that the accumulator is used as + // an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0] + s->r_acc.requireSint16(); - r_temp = READ_VAR(var_type, var_number); - if (r_temp.segment) { - // Pointer arithmetics! - r_temp = pointer_add(s, r_temp, 1); - } else - r_temp = make_reg(0, r_temp.offset + 1); + var_number = opparams[0] + (opcode >= op_plussgi ? s->r_acc.requireSint16() : 0); + r_temp = READ_VAR(var_type, var_number) + 1; PUSH32(r_temp); WRITE_VAR(var_type, var_number, r_temp); break; @@ -2006,14 +1593,15 @@ void run_vm(EngineState *s) { case op_minusap: // 0x73 (115) // Decrement the global, local, temp or param variable and save it // to the accumulator + case op_minusagi: // 0x78 (120) + case op_minusali: // 0x79 (121) + case op_minusati: // 0x7a (122) + case op_minusapi: // 0x7b (123) + // Same as the 4 ones above, except that the accumulator is used as + // an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0]; - r_temp = READ_VAR(var_type, var_number); - if (r_temp.segment) { - // Pointer arithmetics! - s->r_acc = pointer_add(s, r_temp, -1); - } else - s->r_acc = make_reg(0, r_temp.offset - 1); + var_number = opparams[0] + (opcode >= op_minusagi ? s->r_acc.requireSint16() : 0); + s->r_acc = READ_VAR(var_type, var_number) - 1; WRITE_VAR(var_type, var_number, s->r_acc); break; @@ -2023,49 +1611,15 @@ void run_vm(EngineState *s) { case op_minussp: // 0x77 (119) // Decrement the global, local, temp or param variable and save it // to the stack - var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0]; - r_temp = READ_VAR(var_type, var_number); - if (r_temp.segment) { - // Pointer arithmetics! - r_temp = pointer_add(s, r_temp, -1); - } else - r_temp = make_reg(0, r_temp.offset - 1); - PUSH32(r_temp); - WRITE_VAR(var_type, var_number, r_temp); - break; - - case op_minusagi: // 0x78 (120) - case op_minusali: // 0x79 (121) - case op_minusati: // 0x7a (122) - case op_minusapi: // 0x7b (123) - // Decrement the global, local, temp or param variable and save it - // to the accumulator, using the accumulator as an additional index - var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0] + s->r_acc.requireSint16(); - r_temp = READ_VAR(var_type, var_number); - if (r_temp.segment) { - // Pointer arithmetics! - s->r_acc = pointer_add(s, r_temp, -1); - } else - s->r_acc = make_reg(0, r_temp.offset - 1); - WRITE_VAR(var_type, var_number, s->r_acc); - break; - case op_minussgi: // 0x7c (124) case op_minussli: // 0x7d (125) case op_minussti: // 0x7e (126) case op_minusspi: // 0x7f (127) - // Decrement the global, local, temp or param variable and save it - // to the stack, using the accumulator as an additional index + // Same as the 4 ones above, except that the accumulator is used as + // an additional index var_type = opcode & 0x3; // Gets the variable type: g, l, t or p - var_number = opparams[0] + s->r_acc.requireSint16(); - r_temp = READ_VAR(var_type, var_number); - if (r_temp.segment) { - // Pointer arithmetics! - r_temp = pointer_add(s, r_temp, -1); - } else - r_temp = make_reg(0, r_temp.offset - 1); + var_number = opparams[0] + (opcode >= op_minussgi ? s->r_acc.requireSint16() : 0); + r_temp = READ_VAR(var_type, var_number) - 1; PUSH32(r_temp); WRITE_VAR(var_type, var_number, r_temp); break; diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp new file mode 100644 index 0000000000..a165bf7cc3 --- /dev/null +++ b/engines/sci/engine/vm_types.cpp @@ -0,0 +1,247 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "sci/sci.h" +#include "sci/engine/state.h" +#include "sci/engine/seg_manager.h" +#include "sci/engine/vm_types.h" +#include "sci/engine/workarounds.h" + +namespace Sci { + +extern const char *opcodeNames[]; // from scriptdebug.cpp + +reg_t reg_t::lookForWorkaround(const reg_t right) const { + SciTrackOriginReply originReply; + SciWorkaroundSolution solution = trackOriginAndFindWorkaround(0, arithmeticWorkarounds, &originReply); + if (solution.type == WORKAROUND_NONE) + error("arithmetic operation on non-integer (%04x:%04x, %04x:%04x) from method %s::%s (script %d, room %d, localCall %x)", + PRINT_REG(*this), PRINT_REG(right), originReply.objectName.c_str(), + originReply.methodName.c_str(), originReply.scriptNr, g_sci->getEngineState()->currentRoomNumber(), + originReply.localCallOffset); + assert(solution.type == WORKAROUND_FAKE); + return make_reg(0, solution.value); +} + +reg_t reg_t::operator+(const reg_t right) const { + if (isPointer() && isInitialized()) { + // Pointer arithmetics. Only some pointer types make sense here + SegmentObj *mobj = g_sci->getEngineState()->_segMan->getSegmentObj(segment); + + if (!mobj) + error("[VM]: Attempt to add %d to invalid pointer %04x:%04x", right.offset, PRINT_REG(*this)); + + switch (mobj->getType()) { + case SEG_TYPE_LOCALS: + case SEG_TYPE_SCRIPT: + case SEG_TYPE_STACK: + case SEG_TYPE_DYNMEM: + // Make sure that we are adding an offset to the pointer + if (right.isPointer()) + return lookForWorkaround(right); + return make_reg(segment, offset + right.toSint16()); + default: + return lookForWorkaround(right); + } + } else if (isNumber() && isInitialized() && right.isPointer()) { + // Adding a pointer to a number, flip the order + return right + *this; + } else { + // Normal arithmetics. Make sure we're adding a number + if (right.isPointer()) + return lookForWorkaround(right); + // If the current variable is uninitialized, it'll be set + // to zero in order to perform the operation. Such a case + // happens in SQ1, room 28, when throwing the water at Orat. + if (!isInitialized()) + return make_reg(0, right.toSint16()); + else if (!right.isInitialized()) + return *this; + else + return make_reg(0, toSint16() + right.toSint16()); + } +} + +reg_t reg_t::operator-(const reg_t right) const { + if (segment == right.segment) { + // We can subtract numbers, or pointers with the same segment, + // an operation which will yield a number like in C + return make_reg(0, toSint16() - right.toSint16()); + } else { + return *this + make_reg(right.segment, -right.offset); + } +} + +reg_t reg_t::operator*(const reg_t right) const { + if (isNumber() && right.isNumber()) + return make_reg(0, toSint16() * right.toSint16()); + else if (!isInitialized() || !right.isInitialized()) + return NULL_REG; // unitialized variables - always return 0 + else + return lookForWorkaround(right); +} + +reg_t reg_t::operator/(const reg_t right) const { + if (isNumber() && right.isNumber()) { + if (right.isNull()) + return NULL_REG; // division by zero + else + return make_reg(0, toSint16() / right.toSint16()); + } else + return lookForWorkaround(right); +} + +reg_t reg_t::operator>>(const reg_t right) const { + if (isNumber() && right.isNumber()) + return make_reg(0, toUint16() >> right.toUint16()); + else + return lookForWorkaround(right); +} + +reg_t reg_t::operator<<(const reg_t right) const { + if (isNumber() && right.isNumber()) + return make_reg(0, toUint16() << right.toUint16()); + else + return lookForWorkaround(right); +} + +reg_t reg_t::operator+(int16 right) const { + return *this + make_reg(0, right); +} + +reg_t reg_t::operator-(int16 right) const { + return *this - make_reg(0, right); +} + +uint16 reg_t::requireUint16() const { + if (isNumber()) + return toUint16(); + else + return lookForWorkaround(NULL_REG).toUint16(); +} + +int16 reg_t::requireSint16() const { + if (isNumber()) + return toSint16(); + else + return lookForWorkaround(NULL_REG).toSint16(); +} + +reg_t reg_t::operator&(const reg_t right) const { + if (isNumber() && right.isNumber()) + return make_reg(0, toUint16() & right.toUint16()); + else + return lookForWorkaround(right); +} + +reg_t reg_t::operator|(const reg_t right) const { + if (isNumber() && right.isNumber()) + return make_reg(0, toUint16() | right.toUint16()); + else + return lookForWorkaround(right); +} + +reg_t reg_t::operator^(const reg_t right) const { + if (isNumber() && right.isNumber()) + return make_reg(0, toUint16() ^ right.toUint16()); + else + return lookForWorkaround(right); +} + +bool reg_t::operator>(const reg_t right) const { + if (isNumber() && right.isNumber()) + return toSint16() > right.toSint16(); + else if (isPointer() && segment == right.segment) + return toUint16() > right.toUint16(); // pointer comparison + else if (pointerComparisonWithInteger(right)) + return true; + else if (right.pointerComparisonWithInteger(*this)) + return false; + else + return lookForWorkaround(right).toSint16(); +} + +bool reg_t::operator<(const reg_t right) const { + if (isNumber() && right.isNumber()) + return toSint16() < right.toSint16(); + else if (isPointer() && segment == right.segment) + return toUint16() < right.toUint16(); // pointer comparison + else if (pointerComparisonWithInteger(right)) + return false; + else if (right.pointerComparisonWithInteger(*this)) + return true; + else + return lookForWorkaround(right).toSint16(); +} + +bool reg_t::gtU(const reg_t right) const { + if (isNumber() && right.isNumber()) + return toUint16() > right.toUint16(); + else if (isPointer() && segment == right.segment) + return toUint16() > right.toUint16(); // pointer comparison + else if (pointerComparisonWithInteger(right)) + return true; + else if (right.pointerComparisonWithInteger(*this)) + return false; + else + return lookForWorkaround(right).toSint16(); +} + +bool reg_t::ltU(const reg_t right) const { + if (isNumber() && right.isNumber()) + return toUint16() < right.toUint16(); + else if (isPointer() && segment == right.segment) + return toUint16() < right.toUint16(); // pointer comparison + else if (pointerComparisonWithInteger(right)) + return false; + else if (right.pointerComparisonWithInteger(*this)) + return true; + else + return lookForWorkaround(right).toSint16(); +} + +bool reg_t::pointerComparisonWithInteger(const reg_t right) const { + // SCI0/SCI1 scripts use this to check whether a + // parameter is a pointer or a far text + // reference. It is used e.g. by the standard library + // Print function to distinguish two ways of calling it: + // + // (Print "foo") // Pointer to a string + // (Print 420 5) // Reference to the fifth message in text resource 420 + + // It works because in those games, the maximum resource number is 999, + // so any parameter value above that threshold must be a pointer. + // PQ2 japanese compares pointers to 2000 to find out if its a pointer + // or a resource ID. + // There are cases where game scripts check for arbitrary numbers against + // pointers, e.g.: + // Hoyle 3, Pachisi, when any opponent is about to talk + // SQ1, room 28, when throwing water at the Orat + // SQ1, room 58, when giving the ID card to the robot + // Thus we check for all integers <= 2000 + return (isPointer() && right.isNumber() && right.offset <= 2000 && getSciVersion() <= SCI_VERSION_1_LATE); +} + +} // End of namespace Sci diff --git a/engines/sci/engine/vm_types.h b/engines/sci/engine/vm_types.h index cc1f9b2685..0187906670 100644 --- a/engines/sci/engine/vm_types.h +++ b/engines/sci/engine/vm_types.h @@ -41,6 +41,30 @@ struct reg_t { return !(offset || segment); } + uint16 toUint16() const { + return offset; + } + + int16 toSint16() const { + return (int16) offset; + } + + bool isNumber() const { + return !segment; + } + + bool isPointer() const { + return segment != 0 && segment != 0xFFFF; + } + + uint16 requireUint16() const; + int16 requireSint16() const; + + bool isInitialized() const { + return segment != 0xFFFF; + } + + // Comparison operators bool operator==(const reg_t &x) const { return (offset == x.offset) && (segment == x.segment); } @@ -49,39 +73,58 @@ struct reg_t { return (offset != x.offset) || (segment != x.segment); } - uint16 toUint16() const { - return offset; + bool operator>(const reg_t right) const; + bool operator>=(const reg_t right) const { + if (*this == right) + return true; + return *this > right; } - - int16 toSint16() const { - return (int16) offset; + bool operator<(const reg_t right) const; + bool operator<=(const reg_t right) const { + if (*this == right) + return true; + return *this < right; } - uint16 requireUint16() const { - if (isNumber()) - return toUint16(); - else - // The results of this are likely unpredictable... It most likely - // means that a kernel function is returning something wrong. If - // such an error occurs, we usually need to find the last kernel - // function called and check its return value. - error("[VM] Attempt to read unsigned arithmetic value from non-zero segment %04x. Offset: %04x", segment, offset); + // Same as the normal operators, but perform unsigned + // integer checking + bool gtU(const reg_t right) const; + bool geU(const reg_t right) const { + if (*this == right) + return true; + return gtU(right); } - - int16 requireSint16() const { - if (isNumber()) - return toSint16(); - else - // The results of this are likely unpredictable... It most likely - // means that a kernel function is returning something wrong. If - // such an error occurs, we usually need to find the last kernel - // function called and check its return value. - error("[VM] Attempt to read signed arithmetic value from non-zero segment %04x. Offset: %04x", segment, offset); + bool ltU(const reg_t right) const; + bool leU(const reg_t right) const { + if (*this == right) + return true; + return ltU(right); } - bool isNumber() const { - return !segment; - } + bool pointerComparisonWithInteger(const reg_t right) const; + + // Arithmetic operators + reg_t operator+(const reg_t right) const; + reg_t operator-(const reg_t right) const; + reg_t operator*(const reg_t right) const; + reg_t operator/(const reg_t right) const; + reg_t operator>>(const reg_t right) const; + reg_t operator<<(const reg_t right) const; + + reg_t operator+(int16 right) const; + reg_t operator-(int16 right) const; + + void operator+=(const reg_t &right) { *this = *this + right; } + void operator-=(const reg_t &right) { *this = *this - right; } + void operator+=(int16 right) { *this = *this + right; } + void operator-=(int16 right) { *this = *this - right; } + + // Boolean operators + reg_t operator&(const reg_t right) const; + reg_t operator|(const reg_t right) const; + reg_t operator^(const reg_t right) const; + + reg_t lookForWorkaround(const reg_t right) const; }; static inline reg_t make_reg(SegmentId segment, uint16 offset) { diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index faf7662ad6..0666111542 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -34,69 +34,17 @@ namespace Sci { // gameID, room,script,lvl, object-name, method-name, call,index, workaround -const SciWorkaroundEntry opcodeDivWorkarounds[] = { - { GID_QFG1VGA, 301, 928, 0, "Blink", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // when entering inn, gets called with 1 parameter, but 2nd parameter is used for div which happens to be an object - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, room,script,lvl, object-name, method-name, call, index, workaround -const SciWorkaroundEntry opcodeDptoaWorkarounds[] = { - { GID_LSL6, 360, 938, 0, "ROsc", "cycleDone", -1, 0, { WORKAROUND_FAKE, 1 } }, // when looking through tile in the shower room initial cycles get set to an object instead of 2, we fix this by setting 1 after decrease - { GID_LSL6HIRES, 360,64938, 0, "ROsc", "cycleDone", -1, 0, { WORKAROUND_FAKE, 1 } }, // when looking through tile in the shower room initial cycles get set to an object instead of 2, we fix this by setting 1 after decrease - { GID_SQ5, 200, 939, 0, "Osc", "cycleDone", -1, 0, { WORKAROUND_FAKE, 1 } }, // when going back to bridge the crew is goofing off, we get an object as cycle count - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, room,script,lvl, object-name, method-name, call,index, workaround -const SciWorkaroundEntry opcodeGeWorkarounds[] = { - { GID_HOYLE1, 5, 213, 0, "", "export 0", -1, 0, { WORKAROUND_FAKE, 1 } }, // happens sometimes during cribbage - bug #3038433 - { GID_MOTHERGOOSE256, 4, 998, 0, "door", "setCel", -1, 0, { WORKAROUND_FAKE, 1 } }, // after giving the king his pipe back, listening to his song and leaving the castle - bug #3051475 - { GID_PQ3, 31, 31, 0, "rm031", "init", -1, 0, { WORKAROUND_FAKE, 1 } }, // pq3 english: when exiting the car, while morales is making phonecalls - bug #3037565 - { GID_MOTHERGOOSEHIRES,90, 90, 0, "newGameButton", "select", -1, 0, { WORKAROUND_FAKE, 0 } }, // MUMG Deluxe, when selecting "New Game" in the main menu. It tries to compare an integer with a list. Needs to return false for the game to continue. - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, room,script,lvl, object-name, method-name, call,index, workaround -const SciWorkaroundEntry opcodeLeWorkarounds[] = { - { GID_PEPPER, 370, 23, 0, "eastExitFeature", "onMe", -1, 0, { WORKAROUND_FAKE, 1 } }, // Pugh's office, when trying to use either the left or right exits, gets called on an integer and a pointer - bug #3040142 - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, room,script,lvl, object-name, method-name, call,index, workaround -const SciWorkaroundEntry opcodeUltWorkarounds[] = { - { GID_HOYLE3, 400, 0, 1, "Character", "say", -1, 0, { WORKAROUND_FAKE, 0 } }, // While playing Pachisi, when any character starts to talk - bug #3038837 - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, room,script,lvl, object-name, method-name, call,index, workaround -const SciWorkaroundEntry opcodeLaiWorkarounds[] = { - { GID_CAMELOT, 92, 92, 0, "endingCartoon2", "changeState", 0x20d, 0, { WORKAROUND_FAKE, 0 } }, // during the ending, sub gets called with no parameters, uses parameter 1 which is theGrail in this case - bug #3044734 - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, room,script,lvl, object-name, method-name, call,index, workaround -const SciWorkaroundEntry opcodeLsiWorkarounds[] = { - { GID_QFG2, 200, 200, 0, "astro", "messages", -1, 0, { WORKAROUND_FAKE, 0 } }, // when getting asked for your name by the astrologer bug #3039879 - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, room,script,lvl, object-name, method-name, call,index, workaround -const SciWorkaroundEntry opcodeMulWorkarounds[] = { - { GID_FANMADE, 516, 983, 0, "Wander", "setTarget", -1, 0, { WORKAROUND_FAKE, 0 } }, // The Legend of the Lost Jewel Demo (fan made): called with object as second parameter when attacked by insects - bug #3038913 - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, room,script,lvl, object-name, method-name, call,index, workaround -const SciWorkaroundEntry opcodeAndWorkarounds[] = { - { GID_MOTHERGOOSE256, -1, 999, 0, "Event", "new", -1, 0, { WORKAROUND_FAKE, 0 } }, // constantly during the game - // ^^ TODO: which of the mother goose versions is affected by this? EGA? SCI1? SCI1.1? - SCI_WORKAROUNDENTRY_TERMINATOR -}; - -// gameID, room,script,lvl, object-name, method-name, call,index, workaround -const SciWorkaroundEntry opcodeOrWorkarounds[] = { - { GID_ECOQUEST2, 100, 0, 0, "Rain", "points", 0xcc6, 0, { WORKAROUND_FAKE, 0 } }, // when giving the papers to the customs officer, gets called against a pointer instead of a number - bug #3034464 - { GID_MOTHERGOOSE256, -1, 4, 0, "rm004", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // when going north and reaching the castle (rooms 4 and 37) - bug #3038228 +const SciWorkaroundEntry arithmeticWorkarounds[] = { + { GID_CAMELOT, 92, 92, 0, "endingCartoon2", "changeState", 0x20d, 0, { WORKAROUND_FAKE, 0 } }, // op_lai: during the ending, sub gets called with no parameters, uses parameter 1 which is theGrail in this case - bug #3044734 + { GID_ECOQUEST2, 100, 0, 0, "Rain", "points", 0xcc6, 0, { WORKAROUND_FAKE, 0 } }, // op_or: when giving the papers to the customs officer, gets called against a pointer instead of a number - bug #3034464 + { GID_FANMADE, 516, 983, 0, "Wander", "setTarget", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_mul: The Legend of the Lost Jewel Demo (fan made): called with object as second parameter when attacked by insects - bug #3038913 + { GID_ICEMAN, 199, 977, 0, "Grooper", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_add: While dancing with the girl + { GID_MOTHERGOOSE256, -1, 999, 0, "Event", "new", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_and: constantly during the game (SCI1 version) + { GID_MOTHERGOOSE256, -1, 4, 0, "rm004", "doit", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_or: when going north and reaching the castle (rooms 4 and 37) - bug #3038228 + { GID_MOTHERGOOSEHIRES,90, 90, 0, "newGameButton", "select", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_ge: MUMG Deluxe, when selecting "New Game" in the main menu. It tries to compare an integer with a list. Needs to return false for the game to continue. + { GID_QFG1VGA, 301, 928, 0, "Blink", "init", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_div: when entering inn, gets called with 1 parameter, but 2nd parameter is used for div which happens to be an object + { GID_QFG2, 200, 200, 0, "astro", "messages", -1, 0, { WORKAROUND_FAKE, 0 } }, // op_lsi: when getting asked for your name by the astrologer bug #3039879 + { GID_SQ5, 200, 939, 0, "Osc", "cycleDone", -1, 0, { WORKAROUND_FAKE, 1 } }, // op_dpToa: when going back to bridge the crew is goofing off, we get an object as cycle count SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -328,7 +276,7 @@ const SciWorkaroundEntry kGraphRestoreBox_workarounds[] = { { GID_LSL6, -1, 86, 0, "LL6Inv", "show", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens when restoring, is called with hunk segment, but hunk is not allocated at that time // ^^ TODO: check, if this is really a script error or an issue with our restore code { GID_LSL6, -1, 86, 0, "LL6Inv", "hide", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens during the game, gets called with 1 extra parameter - { GID_SQ5, 850, 850, 0, "quirksTurn", "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens while playing Battle Cruiser (invalid segment) - bug #3056811 + { GID_SQ5, 850, 850, 0, NULL, "changeState", -1, 0, { WORKAROUND_STILLCALL, 0 } }, // happens while playing Battle Cruiser (invalid segment) - bug #3056811 SCI_WORKAROUNDENTRY_TERMINATOR }; diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h index 6de971d156..7ab73cdff2 100644 --- a/engines/sci/engine/workarounds.h +++ b/engines/sci/engine/workarounds.h @@ -68,16 +68,7 @@ struct SciWorkaroundEntry { SciWorkaroundSolution newValue; }; -extern const SciWorkaroundEntry opcodeDivWorkarounds[]; -extern const SciWorkaroundEntry opcodeDptoaWorkarounds[]; -extern const SciWorkaroundEntry opcodeGeWorkarounds[]; -extern const SciWorkaroundEntry opcodeLeWorkarounds[]; -extern const SciWorkaroundEntry opcodeUltWorkarounds[]; -extern const SciWorkaroundEntry opcodeLaiWorkarounds[]; -extern const SciWorkaroundEntry opcodeLsiWorkarounds[]; -extern const SciWorkaroundEntry opcodeMulWorkarounds[]; -extern const SciWorkaroundEntry opcodeAndWorkarounds[]; -extern const SciWorkaroundEntry opcodeOrWorkarounds[]; +extern const SciWorkaroundEntry arithmeticWorkarounds[]; extern const SciWorkaroundEntry uninitializedReadWorkarounds[]; extern const SciWorkaroundEntry kAbs_workarounds[]; extern const SciWorkaroundEntry kCelHigh_workarounds[]; diff --git a/engines/sci/module.mk b/engines/sci/module.mk index 005fe21cfa..2202002e2e 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -38,6 +38,7 @@ MODULE_OBJS := \ engine/state.o \ engine/static_selectors.o \ engine/vm.o \ + engine/vm_types.o \ engine/workarounds.o \ graphics/animate.o \ graphics/cache.o \ |