From 544bf83f33d41499b14e891a8eb395e556673b97 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Wed, 15 Jul 2009 08:55:12 +0000 Subject: Created a system for inserting arbitrary code fragments into game scripts, and added an initial fragment to fix the bug of being stuck in the past in the DW1 SCN version svn-id: r42500 --- engines/tinsel/pcode.cpp | 125 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 98 insertions(+), 27 deletions(-) (limited to 'engines/tinsel/pcode.cpp') diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp index a9c6f43d85..ac7bf9970f 100644 --- a/engines/tinsel/pcode.cpp +++ b/engines/tinsel/pcode.cpp @@ -112,6 +112,21 @@ static INT_CONTEXT *icList = 0; static uint32 hMasterScript; +//----------------- SCRIPT BUGS WORKAROUNDS -------------- + +const byte fragment1[] = {(byte)OP_ZERO, (byte) OP_GSTORE | OPSIZE16, 206, 0}; +const int fragment1_size = 4; + +const WorkaroundEntry workaroundList[] = { + // Global 206 in DW1-SCN is whether Rincewind is trying to take the book back to the present. + // In the GRA version, it was global 373, and was reset when he is returned to the past, but + // was forgotten in the SCN version, so this ensures the flag is properly reset + {TINSEL_V1, true, 427942095, 1, fragment1_size, fragment1}, + {TINSEL_V0, false, 0, 0, 0, NULL} +}; + +//----------------- LOCAL GLOBAL DATA -------------------- + /** * Keeps the code array pointer up to date. */ @@ -398,30 +413,67 @@ void SaveInterpretContexts(INT_CONTEXT *sICInfo) { } /** - * Fetch (and sign extend, if necessary) a 8/16/32 bit value from the code - * stream and advance the instruction pointer accordingly. + * Fetches up to 4 bytes from the code script */ -static int32 Fetch(byte opcode, byte *code, int &ip) { - int32 tmp; - if (TinselV0) { - // Fetch a 32 bit value. - tmp = (int32)READ_LE_UINT32(code + ip++ * 4); - } else if (opcode & OPSIZE8) { +static int32 GetBytes(const byte *scriptCode, const WorkaroundEntry* &wkEntry, int &ip, uint numBytes) { + assert(numBytes <= 4 && numBytes != 3); + const byte *code = scriptCode; + + if (wkEntry != NULL) { + if (ip >= wkEntry->numBytes) { + // Finished the workaround + ip = wkEntry->ip; + wkEntry = NULL; + } else { + code = wkEntry->script; + } + } + + uint32 tmp; + switch (numBytes) { + case 0: + // Instruction byte + tmp = code[ip++ * (TinselV0 ? 4 : 1)]; + break; + case 1: // Fetch and sign extend a 8 bit value to 32 bits. - tmp = *(int8 *)(code + ip); - ip += 1; - } else if (opcode & OPSIZE16) { + tmp = (int8)code[ip++]; + break; + case 2: // Fetch and sign extend a 16 bit value to 32 bits. tmp = (int16)READ_LE_UINT16(code + ip); ip += 2; - } else { - // Fetch a 32 bit value. - tmp = (int32)READ_LE_UINT32(code + ip); - ip += 4; + break; + default: + if (TinselV0) + tmp = (int32)READ_LE_UINT32(code + ip++ * 4); + else { + tmp = (int32)READ_LE_UINT32(code + ip); + ip += 4; + } + break; } + return tmp; } +/** + * Fetch (and sign extend, if necessary) a 8/16/32 bit value from the code + * stream and advance the instruction pointer accordingly. + */ +static int32 Fetch(byte opcode, const byte *code, const WorkaroundEntry* &wkEntry, int &ip) { + if (TinselV0) + // Fetch a 32 bit value. + return GetBytes(code, wkEntry, ip, 4); + else if (opcode & OPSIZE8) + // Fetch and sign extend a 8 bit value to 32 bits. + return GetBytes(code, wkEntry, ip, 1); + else if (opcode & OPSIZE16) + return GetBytes(code, wkEntry, ip, 2); + + return GetBytes(code, wkEntry, ip, 4); +} + /** * Interprets the PCODE instructions in the code array. */ @@ -429,7 +481,25 @@ void Interpret(CORO_PARAM, INT_CONTEXT *ic) { do { int tmp, tmp2; int ip = ic->ip; - byte opcode = ic->code[ip++ * (TinselV0 ? 4 : 1)]; + const WorkaroundEntry *wkEntry = ic->fragmentPtr; + + if (wkEntry == NULL) { + // Check to see if a workaround fragment needs to be executed + for (wkEntry = workaroundList; wkEntry->script != NULL; ++wkEntry) { + if ((wkEntry->version == TinselVersion) && + (wkEntry->hCode == ic->hCode) && + (wkEntry->ip == ip) && + (!TinselV1 || (wkEntry->scnFlag == ((_vm->getFeatures() & GF_SCNFILES) != 0)))) { + // Point to start of workaround fragment + ip = 0; + break; + } + } + if (wkEntry->script == NULL) + wkEntry = NULL; + } + + byte opcode = (byte)GetBytes(ic->code, wkEntry, ip, 0); if (TinselV0 && ((opcode & OPMASK) > OP_IMM)) opcode += 3; @@ -447,7 +517,7 @@ void Interpret(CORO_PARAM, INT_CONTEXT *ic) { case OP_FONT: // loads font handle onto stack case OP_PAL: // loads palette handle onto stack - ic->stack[++ic->sp] = Fetch(opcode, ic->code, ip); + ic->stack[++ic->sp] = Fetch(opcode, ic->code, wkEntry, ip); break; case OP_ZERO: // loads zero onto stack @@ -464,31 +534,31 @@ void Interpret(CORO_PARAM, INT_CONTEXT *ic) { case OP_LOAD: // loads local variable onto stack - ic->stack[++ic->sp] = ic->stack[ic->bp + Fetch(opcode, ic->code, ip)]; + ic->stack[++ic->sp] = ic->stack[ic->bp + Fetch(opcode, ic->code, wkEntry, ip)]; break; case OP_GLOAD: // loads global variable onto stack - tmp = Fetch(opcode, ic->code, ip); + tmp = Fetch(opcode, ic->code, wkEntry, ip); assert(0 <= tmp && tmp < numGlobals); ic->stack[++ic->sp] = pGlobals[tmp]; break; case OP_STORE: // pops stack and stores in local variable - ic->stack[ic->bp + Fetch(opcode, ic->code, ip)] = ic->stack[ic->sp--]; + ic->stack[ic->bp + Fetch(opcode, ic->code, wkEntry, ip)] = ic->stack[ic->sp--]; break; case OP_GSTORE: // pops stack and stores in global variable - tmp = Fetch(opcode, ic->code, ip); + tmp = Fetch(opcode, ic->code, wkEntry, ip); assert(0 <= tmp && tmp < numGlobals); pGlobals[tmp] = ic->stack[ic->sp--]; break; case OP_CALL: // procedure call - tmp = Fetch(opcode, ic->code, ip); + tmp = Fetch(opcode, ic->code, wkEntry, ip); //assert(0 <= tmp && tmp < codeSize); // TODO: Verify jumps are not out of bounds ic->stack[ic->sp + 1] = 0; // static link ic->stack[ic->sp + 2] = ic->bp; // dynamic link @@ -499,7 +569,7 @@ void Interpret(CORO_PARAM, INT_CONTEXT *ic) { case OP_LIBCALL: // library procedure or function call - tmp = Fetch(opcode, ic->code, ip); + tmp = Fetch(opcode, ic->code, wkEntry, ip); // NOTE: Interpret() itself is not using the coroutine facilities, // but still accepts a CORO_PARAM, so from the outside it looks // like a coroutine. In fact it may still acts as a kind of "proxy" @@ -538,17 +608,17 @@ void Interpret(CORO_PARAM, INT_CONTEXT *ic) { case OP_ALLOC: // allocate storage on stack - ic->sp += (int32)Fetch(opcode, ic->code, ip); + ic->sp += (int32)Fetch(opcode, ic->code, wkEntry, ip); break; case OP_JUMP: // unconditional jump - ip = Fetch(opcode, ic->code, ip); + ip = Fetch(opcode, ic->code, wkEntry, ip); break; case OP_JMPFALSE: // conditional jump - tmp = Fetch(opcode, ic->code, ip); + tmp = Fetch(opcode, ic->code, wkEntry, ip); if (ic->stack[ic->sp--] == 0) { // condition satisfied - do the jump ip = tmp; @@ -557,7 +627,7 @@ void Interpret(CORO_PARAM, INT_CONTEXT *ic) { case OP_JMPTRUE: // conditional jump - tmp = Fetch(opcode, ic->code, ip); + tmp = Fetch(opcode, ic->code, wkEntry, ip); if (ic->stack[ic->sp--] != 0) { // condition satisfied - do the jump ip = tmp; @@ -660,6 +730,7 @@ void Interpret(CORO_PARAM, INT_CONTEXT *ic) { // check for stack under-overflow assert(ic->sp >= 0 && ic->sp < PCODE_STACK_SIZE); ic->ip = ip; + ic->fragmentPtr = wkEntry; } while (!ic->bHalt); // make sure stack is unwound -- cgit v1.2.3 From aeb2aa6630b49a89992e4f317610b3275ee80ed6 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Thu, 16 Jul 2009 02:27:26 +0000 Subject: Game script fix for bug #2525010 - in the GRA version, the luggage blocked Rincewind's exit from the Inn, leaving him in a non-walkable area svn-id: r42521 --- engines/tinsel/pcode.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'engines/tinsel/pcode.cpp') diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp index ac7bf9970f..581c999848 100644 --- a/engines/tinsel/pcode.cpp +++ b/engines/tinsel/pcode.cpp @@ -116,12 +116,21 @@ static uint32 hMasterScript; const byte fragment1[] = {(byte)OP_ZERO, (byte) OP_GSTORE | OPSIZE16, 206, 0}; const int fragment1_size = 4; +const byte fragment2[] = {OP_LIBCALL | OPSIZE8, 110}; +const int fragment2_size = 2; + const WorkaroundEntry workaroundList[] = { // Global 206 in DW1-SCN is whether Rincewind is trying to take the book back to the present. // In the GRA version, it was global 373, and was reset when he is returned to the past, but // was forgotten in the SCN version, so this ensures the flag is properly reset {TINSEL_V1, true, 427942095, 1, fragment1_size, fragment1}, + + // In DW1-GRA, Rincewind exiting the Inn is blocked by the luggage. Whilst you can then move + // into walkable areas, saving and restoring the game, it will error if you try to move. + // This fragment turns off NPC blocking for the Outside Inn room so that the luggage won't block + {TINSEL_V1, false, 444622076, 0, fragment2_size, fragment2}, + {TINSEL_V0, false, 0, 0, 0, NULL} }; -- cgit v1.2.3 From b04e3e79406031001cd80b40fb21b8f2ca0c5af8 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Thu, 16 Jul 2009 04:06:35 +0000 Subject: Added no blocking fix for Present Outside Inn scene as well svn-id: r42522 --- engines/tinsel/pcode.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'engines/tinsel/pcode.cpp') diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp index 581c999848..8f905916ad 100644 --- a/engines/tinsel/pcode.cpp +++ b/engines/tinsel/pcode.cpp @@ -128,8 +128,11 @@ const WorkaroundEntry workaroundList[] = { // In DW1-GRA, Rincewind exiting the Inn is blocked by the luggage. Whilst you can then move // into walkable areas, saving and restoring the game, it will error if you try to move. - // This fragment turns off NPC blocking for the Outside Inn room so that the luggage won't block + // This fragment turns off NPC blocking for the Outside Inn rooms so that the luggage won't block + // Past Outside Inn {TINSEL_V1, false, 444622076, 0, fragment2_size, fragment2}, + // Present Outside Inn + {TINSEL_V1, false, 352600876, 0, fragment2_size, fragment2}, {TINSEL_V0, false, 0, 0, 0, NULL} }; -- cgit v1.2.3 From f01bd0e9a3277909f85a55b88379348c7bbbebbc Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Thu, 16 Jul 2009 09:31:31 +0000 Subject: Added game script fix for bug #2680397 - game hang when using the brochure on the Beekeeper svn-id: r42527 --- engines/tinsel/pcode.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'engines/tinsel/pcode.cpp') diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp index 8f905916ad..69d64e86fd 100644 --- a/engines/tinsel/pcode.cpp +++ b/engines/tinsel/pcode.cpp @@ -114,19 +114,20 @@ static uint32 hMasterScript; //----------------- SCRIPT BUGS WORKAROUNDS -------------- -const byte fragment1[] = {(byte)OP_ZERO, (byte) OP_GSTORE | OPSIZE16, 206, 0}; +const byte fragment1[] = {OP_ZERO, OP_GSTORE | OPSIZE16, 206, 0}; const int fragment1_size = 4; const byte fragment2[] = {OP_LIBCALL | OPSIZE8, 110}; const int fragment2_size = 2; - +const byte fragment3[] = {OP_ZERO, OP_GSTORE | OPSIZE16, 490 % 256, 490 / 256}; +const int fragment3_size = 4; const WorkaroundEntry workaroundList[] = { - // Global 206 in DW1-SCN is whether Rincewind is trying to take the book back to the present. + // DW1-SCN: Global 206 is whether Rincewind is trying to take the book back to the present. // In the GRA version, it was global 373, and was reset when he is returned to the past, but // was forgotten in the SCN version, so this ensures the flag is properly reset {TINSEL_V1, true, 427942095, 1, fragment1_size, fragment1}, - // In DW1-GRA, Rincewind exiting the Inn is blocked by the luggage. Whilst you can then move + // DW1-GRA: Rincewind exiting the Inn is blocked by the luggage. Whilst you can then move // into walkable areas, saving and restoring the game, it will error if you try to move. // This fragment turns off NPC blocking for the Outside Inn rooms so that the luggage won't block // Past Outside Inn @@ -134,6 +135,13 @@ const WorkaroundEntry workaroundList[] = { // Present Outside Inn {TINSEL_V1, false, 352600876, 0, fragment2_size, fragment2}, + // DW2: In the garden, global #490 is set when the bees begin their 'out of hive' animation, and reset when done. + // But if the game is saved/restored during it, the animation sequence is reset without the global being + // cleared. If the brochure is then used on the beekeeper before the bees do the sequence again, their sequence + // is prevented, and the game goes into an infinite loop waiting for a non-playing animation to finish. + // This fix ensures that the global is reset when the Garden scene is loaded (both entering and restoring a game) + {TINSEL_V2, true, 2888147476, 0, fragment3_size, fragment3}, + {TINSEL_V0, false, 0, 0, 0, NULL} }; -- cgit v1.2.3 From 19e8b39249df45c99bf1a42782664a2a0588c1db Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Thu, 16 Jul 2009 09:53:19 +0000 Subject: Prior script fix also fixes bug #2820788, with stealing wizard's mallets svn-id: r42528 --- engines/tinsel/pcode.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'engines/tinsel/pcode.cpp') diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp index 69d64e86fd..72004b4ae9 100644 --- a/engines/tinsel/pcode.cpp +++ b/engines/tinsel/pcode.cpp @@ -136,9 +136,11 @@ const WorkaroundEntry workaroundList[] = { {TINSEL_V1, false, 352600876, 0, fragment2_size, fragment2}, // DW2: In the garden, global #490 is set when the bees begin their 'out of hive' animation, and reset when done. - // But if the game is saved/restored during it, the animation sequence is reset without the global being - // cleared. If the brochure is then used on the beekeeper before the bees do the sequence again, their sequence - // is prevented, and the game goes into an infinite loop waiting for a non-playing animation to finish. + // But if the game is saved/restored during it, the animation sequence is reset without the global being cleared. + // This causes bugs in several actions which try to disable the bees animation, since they wait indefinitely for + // the global to be cleared, incorrectly believing the animation is currently playing. This includes + // * Giving the brochure to the beekeeper + // * Stealing the mallets from the wizards // This fix ensures that the global is reset when the Garden scene is loaded (both entering and restoring a game) {TINSEL_V2, true, 2888147476, 0, fragment3_size, fragment3}, -- cgit v1.2.3 From ff75d68f1ca292733b601b33ab3cfe4aab670174 Mon Sep 17 00:00:00 2001 From: Torbjörn Andersson Date: Thu, 16 Jul 2009 17:26:44 +0000 Subject: Fixed GCC warning. svn-id: r42536 --- engines/tinsel/pcode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'engines/tinsel/pcode.cpp') diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp index 72004b4ae9..1d73411e13 100644 --- a/engines/tinsel/pcode.cpp +++ b/engines/tinsel/pcode.cpp @@ -142,7 +142,7 @@ const WorkaroundEntry workaroundList[] = { // * Giving the brochure to the beekeeper // * Stealing the mallets from the wizards // This fix ensures that the global is reset when the Garden scene is loaded (both entering and restoring a game) - {TINSEL_V2, true, 2888147476, 0, fragment3_size, fragment3}, + {TINSEL_V2, true, 2888147476U, 0, fragment3_size, fragment3}, {TINSEL_V0, false, 0, 0, 0, NULL} }; -- cgit v1.2.3