1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "sci/sci.h"
24 #include "sci/engine/kernel.h"
25 #include "sci/engine/script.h"
26 #include "sci/engine/state.h"
27 #include "sci/engine/features.h"
28 #include "sci/engine/script_patches.h"
29 #ifdef ENABLE_SCI32
30 #include "sci/engine/guest_additions.h"
31 #endif
32
33 #include "common/util.h"
34
35 namespace Sci {
36
37 // IMPORTANT:
38 // every patch entry needs the following:
39 // - script number (pretty obvious)
40 //
41 // - apply count
42 // specifies the number of times a patch is supposed to get applied.
43 // Most of the time, it should be 1.
44 //
45 // - magicDWORD + magicOffset
46 // please ALWAYS put 0 for those two. Both will get filled out at runtime by the patcher.
47 //
48 // - signature data (is used to identify certain script code, that needs patching)
49 // every signature needs to contain SIG_MAGICDWORD once.
50 // The following 4 bytes after SIG_MAGICDWORD - which don't have to be fixed, you may for example
51 // use SIG_SELECTOR16, will get used to quickly search for a partly match before verifying that
52 // the whole signature actually matches. If it's not included, the script patcher will error() out
53 // right when loading up the game.
54 // If selector-IDs are included, please use SIG_SELECTOR16 + SIG_SELECTOR8 [1]. Simply
55 // specify the selector that way, so that the patcher will search for the specific
56 // selector instead of looking for a hardcoded value. Selectors may not be the same
57 // between game versions.
58 // For UINT16s either use SIG_UINT16 or SIG_SELECTOR16.
59 // Macintosh versions of SCI games are using BE ordering instead of LE since SCI1.1 for UINT16s in scripts
60 // By using those 2 commands, it's possible to make patches work for PC and Mac versions of the same game.
61 // You may also skip bytes by using the SIG_ADDTOOFFSET command
62 // Every signature data needs to get terminated using SIGNATURE_END
63 //
64 // - patch data (is used for actually patching scripts)
65 // When a match is found, the patch data will get applied.
66 // Patch data is similar to signature data. Just use PATCH_SELECTOR16 + PATCH_SELECTOR8 [1]
67 // for patching in selectors.
68 // There are also patch specific commands.
69 // Those are PATCH_GETORIGINALBYTE, which fetches a byte from the original script
70 // and PATCH_GETORIGINALBYTEADJUST, which does the same but gets a second value
71 // from the uint16 array and uses that value to adjust the original byte.
72 // Every patch data needs to get terminated using PATCH_END
73 //
74 // - and please always add a comment about why the patch was done and what's causing issues.
75 // If possible make sure, that the patch works on localized (or just different) game versions
76 // as well in case those need patching too.
77 //
78 // [1] - selectors need to get specified in selectorTable[] and ScriptPatcherSelectors-enum
79 // before they can get used using the SIG_SELECTORx and PATCH_SELECTORx commands.
80 // You have to use the exact same order in both the table and the enum, otherwise
81 // it won't work.
82 // ATTENTION: selectors will only work here, when they are also in SelectorCache (selector.h)
83
84 static const char *const selectorNameTable[] = {
85 "cycles", // system selector
86 "seconds", // system selector
87 "init", // system selector
88 "dispose", // system selector
89 "new", // system selector
90 "curEvent", // system selector
91 "disable", // system selector
92 "doit", // system selector
93 "show", // system selector
94 "x", // system selector
95 "cel", // system selector
96 "setMotion", // system selector
97 "overlay", // system selector
98 "setPri", // system selector - for setting priority
99 "play", // system selector
100 "number", // system selector
101 "setScript", // system selector
102 "setCycle", // system selector
103 "setStep", // system selector
104 "cycleSpeed", // system selector
105 "handsOff", // system selector
106 "handsOn", // system selector
107 "type", // system selector
108 "localize", // Freddy Pharkas
109 "roomFlags", // Iceman
110 "put", // Police Quest 1 VGA
111 "approachVerbs", // Police Quest 1 VGA, QFG4
112 "newRoom", // Police Quest 3, GK1
113 "changeState", // Quest For Glory 1 VGA, QFG4
114 "hide", // Quest For Glory 1 VGA, QFG4
115 "say", // Quest For Glory 1 VGA, QFG4
116 "script", // Quest For Glory 1 VGA
117 "solvePuzzle", // Quest For Glory 3
118 "curIcon", // Quest For Glory 3, QFG4
119 "curInvIcon", // Quest For Glory 3, QFG4
120 "timesShownID", // Space Quest 1 VGA
121 "startText", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support
122 "startAudio", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support
123 "modNum", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support
124 "has", // King's Quest 6, GK1
125 "modeless", // King's Quest 6 CD
126 "cycler", // Space Quest 4 / system selector
127 "setCel", // Space Quest 4, Phant2, GK1
128 "addToPic", // Space Quest 4
129 "stop", // Space Quest 4
130 "canControl", // Space Quest 4
131 "looper", // Space Quest 4
132 "nMsgType", // Space Quest 4
133 "doVerb", // Space Quest 4
134 "setRegions", // Space Quest 4
135 "setSpeed", // Space Quest 5, QFG4
136 "loop", // Laura Bow 1 Colonel's Bequest, QFG4
137 "setLoop", // Laura Bow 1 Colonel's Bequest, QFG4
138 "ignoreActors", // Laura Bow 1 Colonel's Bequest
139 "setVol", // Laura Bow 2 CD
140 "at", // Longbow, QFG4
141 "owner", // Longbow, QFG4
142 "delete", // EcoQuest 1
143 "size", // EcoQuest 1
144 "signal", // EcoQuest 1, GK1
145 "obstacles", // EcoQuest 1, QFG4
146 "handleEvent", // EcoQuest 2, Shivers
147 #ifdef ENABLE_SCI32
148 "newWith", // SCI2 array script
149 "posn", // SCI2 benchmarking script
150 "printLang", // GK2
151 "view", // RAMA benchmarking, GK1, QFG4
152 "fade", // Shivers
153 "test", // Torin
154 "get", // Torin, GK1
155 "normalize", // GK1
156 "set", // Torin
157 "clear", // Torin
158 "masterVolume", // SCI2 master volume reset
159 "data", // Phant2, QFG4
160 "format", // Phant2
161 "setSize", // Phant2
162 "iconV", // Phant2
163 "update", // Phant2
164 "xOff", // Phant2
165 "fore", // KQ7
166 "back", // KQ7
167 "font", // KQ7
168 "setHeading", // KQ7
169 "setScale", // LSL6hires, QFG4
170 "setScaler", // LSL6hires, QFG4
171 "readWord", // LSL7, Phant1, Torin
172 "points", // PQ4
173 "select", // PQ4
174 "addObstacle", // QFG4
175 "handle", // RAMA
176 "saveFilePtr", // RAMA
177 "priority", // RAMA
178 "plane", // RAMA
179 "state", // RAMA
180 "getSubscriberObj", // RAMA
181 "advanceCurIcon", // QFG4
182 "amount", // QFG4
183 "claimed", // QFG4
184 "cue", // QFG4
185 "getCursor", // QFG4
186 "heading", // QFG4
187 "moveSpeed", // QFG4
188 "register", // QFG4
189 "sayMessage", // QFG4
190 "setCursor", // QFG4
191 "setLooper", // QFG4
192 "useStamina", // QFG4
193 "value", // QFG4
194 #endif
195 NULL
196 };
197
198 enum ScriptPatcherSelectors {
199 SELECTOR_cycles = 0,
200 SELECTOR_seconds,
201 SELECTOR_init,
202 SELECTOR_dispose,
203 SELECTOR_new,
204 SELECTOR_curEvent,
205 SELECTOR_disable,
206 SELECTOR_doit,
207 SELECTOR_show,
208 SELECTOR_x,
209 SELECTOR_cel,
210 SELECTOR_setMotion,
211 SELECTOR_overlay,
212 SELECTOR_setPri,
213 SELECTOR_play,
214 SELECTOR_number,
215 SELECTOR_setScript,
216 SELECTOR_setCycle,
217 SELECTOR_setStep,
218 SELECTOR_cycleSpeed,
219 SELECTOR_handsOff,
220 SELECTOR_handsOn,
221 SELECTOR_type,
222 SELECTOR_localize,
223 SELECTOR_roomFlags,
224 SELECTOR_put,
225 SELECTOR_approachVerbs,
226 SELECTOR_newRoom,
227 SELECTOR_changeState,
228 SELECTOR_hide,
229 SELECTOR_say,
230 SELECTOR_script,
231 SELECTOR_solvePuzzle,
232 SELECTOR_curIcon,
233 SELECTOR_curInvIcon,
234 SELECTOR_timesShownID,
235 SELECTOR_startText,
236 SELECTOR_startAudio,
237 SELECTOR_modNum,
238 SELECTOR_has,
239 SELECTOR_modeless,
240 SELECTOR_cycler,
241 SELECTOR_setCel,
242 SELECTOR_addToPic,
243 SELECTOR_stop,
244 SELECTOR_canControl,
245 SELECTOR_looper,
246 SELECTOR_nMsgType,
247 SELECTOR_doVerb,
248 SELECTOR_setRegions,
249 SELECTOR_setSpeed,
250 SELECTOR_loop,
251 SELECTOR_setLoop,
252 SELECTOR_ignoreActors,
253 SELECTOR_setVol,
254 SELECTOR_at,
255 SELECTOR_owner,
256 SELECTOR_delete,
257 SELECTOR_size,
258 SELECTOR_signal,
259 SELECTOR_obstacles,
260 SELECTOR_handleEvent
261 #ifdef ENABLE_SCI32
262 ,
263 SELECTOR_newWith,
264 SELECTOR_posn,
265 SELECTOR_printLang,
266 SELECTOR_view,
267 SELECTOR_fade,
268 SELECTOR_test,
269 SELECTOR_get,
270 SELECTOR_normalize,
271 SELECTOR_set,
272 SELECTOR_clear,
273 SELECTOR_masterVolume,
274 SELECTOR_data,
275 SELECTOR_format,
276 SELECTOR_setSize,
277 SELECTOR_iconV,
278 SELECTOR_update,
279 SELECTOR_xOff,
280 SELECTOR_fore,
281 SELECTOR_back,
282 SELECTOR_font,
283 SELECTOR_setHeading,
284 SELECTOR_setScale,
285 SELECTOR_setScaler,
286 SELECTOR_readWord,
287 SELECTOR_points,
288 SELECTOR_select,
289 SELECTOR_addObstacle,
290 SELECTOR_handle,
291 SELECTOR_saveFilePtr,
292 SELECTOR_priority,
293 SELECTOR_plane,
294 SELECTOR_state,
295 SELECTOR_getSubscriberObj,
296 SELECTOR_advanceCurIcon,
297 SELECTOR_amount,
298 SELECTOR_claimed,
299 SELECTOR_cue,
300 SELECTOR_getCursor,
301 SELECTOR_heading,
302 SELECTOR_moveSpeed,
303 SELECTOR_register,
304 SELECTOR_sayMessage,
305 SELECTOR_setCursor,
306 SELECTOR_setLooper,
307 SELECTOR_useStamina,
308 SELECTOR_value
309 #endif
310 };
311
312 #ifdef ENABLE_SCI32
313 // It is not possible to change the directory for ScummVM save games, so disable
314 // the "change directory" button in the standard save dialogue
315 static const uint16 sci2ChangeDirSignature[] = {
316 0x72, SIG_ADDTOOFFSET(+2), // lofsa changeDirI
317 0x4a, SIG_UINT16(0x0004), // send 4
318 SIG_MAGICDWORD,
319 0x36, // push
320 0x35, 0xf7, // ldi $f7
321 0x12, // and
322 0x36, // push
323 SIG_END
324 };
325
326 static const uint16 sci2ChangeDirPatch[] = {
327 PATCH_ADDTOOFFSET(+3), // lofsa changeDirI
328 PATCH_ADDTOOFFSET(+3), // send 4
329 PATCH_ADDTOOFFSET(+1), // push
330 0x35, 0x00, // ldi 0
331 PATCH_END
332 };
333
334 // Save game script hardcodes the maximum number of save games to 20, but
335 // this is an artificial constraint that does not apply to ScummVM
336 static const uint16 sci2NumSavesSignature1[] = {
337 SIG_MAGICDWORD,
338 0x8b, 0x02, // lsl local[2]
339 0x35, 0x14, // ldi 20
340 0x22, // lt?
341 SIG_END
342 };
343
344 static const uint16 sci2NumSavesPatch1[] = {
345 PATCH_ADDTOOFFSET(+2), // lsl local[2]
346 0x35, 0x63, // ldi 99
347 PATCH_END
348 };
349
350 static const uint16 sci2NumSavesSignature2[] = {
351 SIG_MAGICDWORD,
352 0x8b, 0x02, // lsl local[2]
353 0x35, 0x14, // ldi 20
354 0x1a, // eq?
355 SIG_END
356 };
357
358 static const uint16 sci2NumSavesPatch2[] = {
359 PATCH_ADDTOOFFSET(+2), // lsl local[2]
360 0x35, 0x63, // ldi 99
361 PATCH_END
362 };
363
364 // Phantasmagoria & SQ6 try to initialize the first entry of an int16 array
365 // using an empty string, which is not valid (it should be a number)
366 static const uint16 sci21IntArraySignature[] = {
367 0x38, SIG_SELECTOR16(newWith), // pushi newWith
368 0x7a, // push2
369 0x39, 0x04, // pushi $4
370 0x72, SIG_ADDTOOFFSET(+2), // lofsa string ""
371 SIG_MAGICDWORD,
372 0x36, // push
373 0x51, 0x0b, // class IntArray
374 0x4a, 0x08, // send $8
375 SIG_END
376 };
377
378 static const uint16 sci21IntArrayPatch[] = {
379 PATCH_ADDTOOFFSET(+6), // push $b9; push2; pushi $4
380 0x76, // push0
381 0x34, PATCH_UINT16(0x0001), // ldi 0001 (waste bytes)
382 PATCH_END
383 };
384
385 // Most SCI32 games have a video performance benchmarking loop at the
386 // beginning of the game. Running this benchmark with calls to
387 // `OSystem::updateScreen` will often cause the benchmark to return a low value,
388 // which causes games to disable some visual effects. Running without calls to
389 // `OSystem::updateScreen` on any reasonably modern CPU will cause the benchmark
390 // to overflow, leading to randomly disabled effects. This patch changes the
391 // benchmarking code to always return the game's maximum speed value.
392 //
393 // Applies to at least: GK1 floppy, PQ4 floppy, PQ4CD, LSL6hires, Phant1,
394 // Shivers, SQ6
395 static const uint16 sci2BenchmarkSignature[] = {
396 SIG_MAGICDWORD,
397 0x38, SIG_SELECTOR16(init), // pushi init
398 0x76, // push0
399 0x38, SIG_SELECTOR16(posn), // pushi posn
400 SIG_END
401 };
402
403 static const uint16 sci2BenchmarkPatch[] = {
404 0x7a, // push2
405 0x38, SIG_UINT16(64908), // pushi 64908
406 0x76, // push0
407 0x43, 0x03, SIG_UINT16(0x04), // callk DisposeScript[3], 4
408 0x48, // ret
409 PATCH_END
410 };
411
412 // The init code that runs in many SCI32 games unconditionally resets the music
413 // volume, but the game should always use the volume stored in ScummVM.
414 // Applies to at least: LSL6hires, MGDX, PQ:SWAT, QFG4
415 static const uint16 sci2VolumeResetSignature[] = {
416 SIG_MAGICDWORD,
417 0x38, SIG_SELECTOR16(masterVolume), // pushi masterVolume
418 0x78, // push1
419 0x39, SIG_ADDTOOFFSET(+1), // pushi [default volume]
420 0x81, 0x01, // lag global[1]
421 0x4a, SIG_UINT16(0x0006), // send 6
422 SIG_END
423 };
424
425 static const uint16 sci2VolumeResetPatch[] = {
426 0x32, PATCH_UINT16(0x0008), // jmp 8 [past volume reset]
427 PATCH_END
428 };
429
430 // At least Gabriel Knight 1 and Police Quest 4 floppy have a broken Str::strip inside script 64918.
431 // The code never passes over the actual string to kStringTrim, so that would not work and also trigger
432 // a signature mismatch.
433 // Localized version of Police Quest 4 were also affected.
434 // Gabriel Knight although affected doesn't seem to ever call the code, so there is no reason to patch it.
435 // Police Quest 4 CD got this fixed.
436 static const uint16 sci2BrokenStrStripSignature[] = {
437 SIG_MAGICDWORD,
438 0x85, 0x06, // lat temp[6]
439 0x31, 0x10, // bnt [jump to code that passes 2 parameters]
440 0x38, SIG_UINT16(0x00c2), // pushi 00c2 (callKernel)
441 0x38, SIG_UINT16(0x0003), // pushi 03
442 0x39, 0x0e, // pushi 0e
443 0x8d, 0x0b, // lst temp[0b]
444 0x36, // push
445 0x54, SIG_UINT16(0x000a), // self 0a
446 0x33, 0x0b, // jmp [ret]
447 // 2 parameter code
448 0x38, SIG_UINT16(0x00c2), // pushi 00c2
449 0x7a, // push2
450 0x39, 0x0e, // pushi 0e
451 0x8d, 0x0b, // lst temp[0b]
452 0x54, SIG_UINT16(0x0008), // self 08
453 SIG_END
454 };
455
456 static const uint16 sci2BrokenStrStripPatch[] = {
457 PATCH_ADDTOOFFSET(+2),
458 0x85, 0x06, // lat temp[6] (once more]
459 PATCH_ADDTOOFFSET(+3), // jump over pushi callKernel
460 0x39, 0x04, // pushi 04
461 0x39, 0x0e, // pushi 0e
462 // Attention: data is 0x14 in PQ4 CD, in floppy it's 0x12
463 0x67, 0x12, // pTos data (pass actual data)
464 0x8d, 0x0b, // lst temp[0b]
465 0x36, // push
466 0x54, PATCH_UINT16(0x000c), // self 0c
467 0x48, // ret
468 PATCH_END
469 };
470
471 // Torin/LSL7-specific version of sci2NumSavesSignature1/2
472 // Applies to at least: English CD
473 static const uint16 torinLarry7NumSavesSignature[] = {
474 SIG_MAGICDWORD,
475 0x36, // push
476 0x35, 0x14, // ldi 20
477 0x20, // ge?
478 SIG_END
479 };
480
481 static const uint16 torinLarry7NumSavesPatch[] = {
482 PATCH_ADDTOOFFSET(+1), // push
483 0x35, 0x63, // ldi 99
484 PATCH_END
485 };
486
487 #endif
488
489 // ===========================================================================
490 // Conquests of Camelot
491 // At the bazaar in Jerusalem, it's possible to see a girl taking a shower.
492 // If you get too close, you get warned by the father - if you don't get away,
493 // he will kill you.
494 // Instead of walking there manually, it's also possible to enter "look window"
495 // and ego will automatically walk to the window. It seems that this is something
496 // that wasn't properly implemented, because instead of getting killed, you will
497 // get an "Oops" message in Sierra SCI.
498 //
499 // This is caused by peepingTom in script 169 not getting properly initialized.
500 // peepingTom calls the object behind global[b9h]. This global variable is
501 // properly initialized when walking there manually (method fawaz::doit).
502 // When you instead walk there automatically (method fawaz::handleEvent), that
503 // global isn't initialized, which then results in the Oops-message in Sierra SCI
504 // and an error message in ScummVM/SCI.
505 //
506 // We fix the script by patching in a jump to the proper code inside fawaz::doit.
507 // Responsible method: fawaz::handleEvent
508 // Fixes bug: #6402
509 static const uint16 camelotSignaturePeepingTom[] = {
510 0x72, SIG_MAGICDWORD, SIG_UINT16(0x077e), // lofsa fawaz <-- start of proper initializion code
511 0xa1, 0xb9, // sag global[b9h]
512 SIG_ADDTOOFFSET(+571), // ...
513 0x39, 0x7a, // pushi 7a <-- initialization code when walking automatically
514 0x78, // push1
515 0x7a, // push2
516 0x38, SIG_UINT16(0x00a9), // pushi 00a9 - script 169
517 0x78, // push1
518 0x43, 0x02, 0x04, // callk ScriptID
519 0x36, // push
520 0x81, 0x00, // lag global[0]
521 0x4a, 0x06, // send 06
522 0x32, SIG_UINT16(0x0520), // jmp [end of fawaz::handleEvent]
523 SIG_END
524 };
525
526 static const uint16 camelotPatchPeepingTom[] = {
527 PATCH_ADDTOOFFSET(+576),
528 0x32, PATCH_UINT16(0xfdbd), // jmp [to fawaz::doit] (properly init peepingTom code)
529 PATCH_END
530 };
531
532 // If the butcher's daughter in room 62 closes her window while Arthur interacts
533 // with the relic merchant then the game locks up. The script daughterAppears
534 // attempts to dispose the script peepingTom, but it does so by clearing ego's
535 // script no matter what it is, which breaks the game if the script is buyRelic
536 // or one of the other handsOff merchant scripts.
537 //
538 // We fix this by calling peepingTom:dispose instead of clearing ego's script.
539 // As this is an earlier SCI game prior to Script:dispose clearing the client
540 // property, we need to do that ourselves in peepingTom:doit when Arthur turns
541 // away from the window, or else peepingTom:client will still point to ego
542 // after disposal, and the subsequent peepingTom:dispose will end ego's script.
543 //
544 // Applies to: All versions
545 // Responsible methods: daughterAppears:changeState(6), peepingTom:doit
546 // Fixes bug: #11025
547 static const uint16 camelotSignatureRelicMerchantLockup1[] = {
548 0x39, SIG_SELECTOR8(setScript), // pushi setScript
549 0x78, // push1
550 0x76, // push0
551 0x81, SIG_MAGICDWORD, 0x00, // lag 00
552 0x4a, 0x06, // send 06 [ ego setScript: 0 ]
553 0x3a, // toss
554 SIG_END
555 };
556
557 static const uint16 camelotPatchRelicMerchantLockup1[] = {
558 0x39, PATCH_SELECTOR8(dispose), // pushi dispose
559 0x76, // push0
560 0x72, PATCH_UINT16(0x01f3), // lofsa peepingTom
561 0x4a, 0x04, // send 04 [ peepingTom dispose: ]
562 PATCH_END
563 };
564
565 static const uint16 camelotSignatureRelicMerchantLockup2[] = {
566 0x39, SIG_SELECTOR8(setScript), // pushi setScript
567 0x78, // push1
568 0x76, // push0
569 0x81, SIG_MAGICDWORD, 0x00, // lag 00
570 0x4a, 0x06, // send 06 [ ego setScript: 0 ]
571 0x48, // ret
572 SIG_END
573 };
574
575 static const uint16 camelotPatchRelicMerchantLockup2[] = {
576 0x39, PATCH_SELECTOR8(dispose), // pushi dispose
577 0x76, // push0
578 0x54, 0x04, // self 04 [ self dispose: ]
579 0x76, // push0
580 0x69, 0x08, // sTop client [ client = 0 ]
581 PATCH_END
582 };
583
584 // The hunter in room 11 doesn't award soul points if you buy his furs with the
585 // "buy furs" command first and "pay" second. Two points are awarded if these
586 // commands are entered in the opposite order.
587 //
588 // We fix this by adding the missing function call to award the soul points as
589 // Sierra did in later versions. Fortunately, the GiveMoney script contains
590 // redundant code that can be replaced as it is occurs later in the method.
591 //
592 // Applies to: PC only
593 // Responsible method: GiveMoney:changeState(3)
594 // Fixes bug: #11027
595 static const uint16 camelotSignatureHunterMissingPoints[] = {
596 SIG_MAGICDWORD,
597 0x30, SIG_UINT16(0x0020), // bnt 0020 [ matches PC only ]
598 0x35, 0x00, // ldi 00
599 0xa3, 0x00, // sal 00 [ local0 = 0 ]
600 // unnecessary code which is repeated later in the method
601 0x89, 0xdd, // lsg dd
602 0x81, 0x84, // lag 84
603 0x02, // add
604 0xa1, 0xdd, // sag dd [ global221 += global132 ]
605 0x35, 0x00, // ldi 00
606 0xa1, 0x84, // sag 84 [ global132 = 0 ]
607 SIG_END
608 };
609
610 static const uint16 camelotPatchHunterMissingPoints[] = {
611 PATCH_ADDTOOFFSET(+7),
612 0x38, PATCH_UINT16(0x0003), // pushi 0003
613 0x38, PATCH_UINT16(0x00f5), // pushi 00f5 [ point flag 245 ]
614 0x7a, // push2 [ soul points ]
615 0x7a, // push2 [ +2 points ]
616 0x45, 0x0a, 0x06, // callb proc0_10 06 [ +2 soul points ]
617 PATCH_END
618 };
619
620 // When giving away the mule in room 56, the merchant's first long message is
621 // immediately replaced by the next. mo:handleEvent displays the first and
622 // starts the script getMule, which proceeds to display its own messages
623 // without waiting.
624 //
625 // We fix this by adding code to getMule to wait for the merchant's message to
626 // complete if you gave away the mule and a message is on screen.
627 //
628 // Applies to: All versions
629 // Responsible method: getMule:changeState(4)
630 // Fixes bug: #11026
631 static const uint16 camelotSignatureGiveMuleMessage[] = {
632 0x30, SIG_UINT16(0x0023), // bnt 0023 [ state 4 ]
633 SIG_ADDTOOFFSET(+0x20),
634 SIG_MAGICDWORD,
635 0x32, SIG_UINT16(0x0239), // jmp 0239 [ end of method ]
636 0x3c, // dup
637 0x35, 0x04, // ldi 04
638 0x1a, // eq?
639 0x30, SIG_UINT16(0x0016), // bnt 0016 [ state 5 ]
640 0x83, 0x02, // lal 02 [ gave away mule? ]
641 0x30, SIG_UINT16(0x000a), // bnt 000a [ skip state 14 if mule was sold ]
642 0x39, SIG_SELECTOR8(changeState), // pushi changeState
643 0x78, // push1
644 0x39, 0x0e, // pushi 0e
645 0x54, 0x06, // self 06 [ self changeState: 14 ]
646 0x32, SIG_UINT16(0x0223), // jmp 0223 [ end of method ]
647 0x35, 0x01, // ldi 01
648 0x65, 0x10, // aTop cycles [ cycles = 1 ]
649 0x32, SIG_UINT16(0x021c), // jmp 021c [ end of method ]
650 SIG_END
651 };
652
653 static const uint16 camelotPatchGiveMuleMessage[] = {
654 0x30, PATCH_UINT16(0x0020), // bnt 0020 [ state 4 ]
655 PATCH_ADDTOOFFSET(+0x20),
656 0x3c, // dup
657 0x35, 0x04, // ldi 04
658 0x1a, // eq?
659 0x31, 0x1a, // bnt 1a [ state 5 ]
660 0x83, 0x02, // lal 02 [ gave away mule? ]
661 0x31, 0x13, // bnt 13 [ skip state 14 if mule was sold ]
662 0x35, 0x0d, // ldi 0d
663 0x65, 0x0a, // aTop state [ state = 13 ]
664 0x38, PATCH_UINT16(0x0121), // pushi talkCue [ same value in all versions ]
665 0x78, // push1
666 0x7c, // pushSelf
667 0x38, PATCH_UINT16(0x0122), // pushi tS1 [ same value in all versions ]
668 0x76, // push0
669 0x81, 0x6f, // lag 6f
670 0x4a, 0x0a, // send 0a [ tObj talkCue: self tS1? ]
671 0x2f, 0x03, // bt 03 [ don't set cycles if message on screen ]
672 0x78, // push1
673 0x69, 0x10, // sTop cycles [ cycles = 1 ]
674 PATCH_END
675 };
676
677 // In Fatima's house in room 64, "look room" and "look trap" respond with the
678 // wrong messages due to testing the wrong flag. Flag 162 is set when falling
679 // through the trap door and alters responses the next time in the room, but
680 // the script tests flag 137 instead, which is set when entering the room.
681 //
682 // Sierra fixed the first flag test in Amiga and Atari ST but not the second, so
683 // this patch is applied only once to those versions and twice to PC.
684 //
685 // Applies to: All versions
686 // Responsible method: Rm64:handleEvent
687 // Fixes bug: #11028
688 static const uint16 camelotSignatureFatimaRoomMessages[] = {
689 0x78, // push1
690 0x38, SIG_MAGICDWORD, // pushi 0089 [ flag 137, always true ]
691 SIG_UINT16(0x0089),
692 0x45, 0x09, 0x02, // callb proc0_9 02 [ is flag 137 set? ]
693 SIG_END
694 };
695
696 static const uint16 camelotPatchFatimaRoomMessages[] = {
697 PATCH_ADDTOOFFSET(+1),
698 0x38, PATCH_UINT16(0x00a2), // pushi 00a2 [ flag 162, set by trap ]
699 PATCH_END
700 };
701
702 // Sheathing the sword by pressing F8 while entering or exiting a room breaks
703 // the game by placing ego in an invalid state that allows walking through
704 // obstacles and prevents room changes. This affects rooms that subclass eRoom
705 // in areas that allow walking with sword drawn such as the monk's ruins and
706 // the desert. This is most likely to occur while battling the monk.
707 //
708 // eRoom walks ego in and out of rooms in handsOff mode. It sets ego:illegalBits
709 // to 0, enables ignoreActors, and sets a motion that cues when complete to
710 // restore ego's properties. Sheathing the sword interrupts the motion, which
711 // prevents eRoom:cue, and prematurely restores control to the user with ego in
712 // the temporary state. There are almost no restrictions on sheathing because
713 // it's used when input is disabled and during several handsOff scenes.
714 //
715 // We fix this by preventing sword scripts from starting when an eRoom is in the
716 // middle of controlling ego. eRoom tracks this with the comingIn and goingOut
717 // properties and we require that both be cleared to execute a sword command.
718 //
719 // Applies to: All versions
720 // Responsible method: ARTHUR:doit
721 // Fixes bug: #11042
722 static const uint16 camelotSignatureSwordSheathing[] = {
723 SIG_MAGICDWORD,
724 0x89, 0x7d, // lsg 7d [ sword-command ]
725 0x3c, // dup
726 0x35, 0x01, // ldi 01
727 0x1a, // eq? [ sword-command == 1 (draw) ]
728 0x30, SIG_UINT16(0x0013), // bnt 0013
729 0x39, SIG_SELECTOR8(setScript), // pushi setScript
730 0x78, // push1
731 0x7a, // push2
732 0x38, SIG_UINT16(0x038e), // pushi 910d
733 0x76, // push0
734 0x43, 0x02, 0x04, // callk ScriptID 04 [ ScriptID 910 0 (DrawSword) ]
735 0x36, // push
736 0x81, 0x00, // lag 00
737 0x4a, 0x06, // send 06 [ ego setScript: DrawSword ]
738 0x32, SIG_UINT16(0x0085), // jmp 0085 [ end of switch ]
739 0x3c, // dup
740 0x35, 0x02, // ldi 02
741 0x1a, // eq? [ sword-command == 2 (sheathe) ]
742 0x30, SIG_UINT16(0x0013), // bnt 0013
743 0x39, SIG_SELECTOR8(setScript), // pushi setScript
744 0x78, // push1
745 0x7a, // push2
746 0x38, SIG_UINT16(0x038e), // pushi 910d
747 0x78, // push1
748 0x43, 0x02, 0x04, // callk ScriptID 04 [ ScriptID 910 1 (SheatheSword) ]
749 0x36, // push
750 0x81, 0x00, // lag 00
751 0x4a, 0x06, // send 06 [ ego setScript: SheatheSword ]
752 0x32, SIG_UINT16(0x006b), // jmp 006b [ end of switch ]
753 0x3c, // dup
754 0x35, 0x03, // ldi 03
755 0x1a, // eq? [ sword-command == 3 (parry) ]
756 0x30, SIG_UINT16(0x0013), // bnt 0013
757 0x39, SIG_SELECTOR8(setScript), // pushi setScript
758 0x78, // push1
759 0x7a, // push2
760 0x38, SIG_UINT16(0x0390), // pushi 912d
761 0x78, // push1
762 0x43, 0x02, 0x04, // callk ScriptID 04 [ ScriptID 912 1 (DoParry) ]
763 SIG_ADDTOOFFSET(+15),
764 0x81, 0x7c, // lag 7c [ start of sword-command == 0 handler ]
765 SIG_ADDTOOFFSET(+72),
766 0x3a, // toss [ end of switch ]
767 SIG_END
768 };
769
770 static const uint16 camelotPatchSwordSheathing[] = {
771 0x81, 0x7d, // lag 7d [ sword-command ]
772 0x31, 0x53, // bnt 53 [ sword-command == 0 handler ]
773 0x39, 0x03, // pushi 03
774 0x20, // ge? [ 3 >= sword-command ]
775 0x31, 0x3e, // bnt 3e [ exit if sword-command > 3 ]
776 0x7a, // push2
777 0x89, 0x02, // lsg 02
778 0x38, PATCH_UINT16(0x014b), // pushi comingIn [ same value in all versions ]
779 0x43, 0x07, 0x04, // callk RespondsTo 04 [ RespondsTo currentRoom comingIn ]
780 0x31, 0x14, // bnt 14 [ skip eRoom checks if room isn't an eRoom ]
781 0x38, PATCH_UINT16(0x014b), // pushi comingIn
782 0x76, // push0
783 0x81, 0x02, // lag 02
784 0x4a, 0x04, // send 04 [ currentRoom comingIn? ]
785 0x2f, 0x29, // bt 29 [ skip sword scripts if entering room ]
786 0x38, PATCH_UINT16(0x014c), // pushi goingOut [ same value in all versions ]
787 0x76, // push0
788 0x81, 0x02, // lag 02
789 0x4a, 0x04, // send 04 [ currentRoom goingOut? ]
790 0x2f, 0x1f, // bt 1f [ skip sword scripts if exiting room ]
791 0x39, PATCH_SELECTOR8(setScript), // pushi setScript
792 0x78, // push1
793 0x7a, // push2
794 0x81, 0x7d, // lag 7d
795 0x7a, // push2
796 0x20, // ge? [ 2 >= sword-command ]
797 0x31, 0x05, // bnt 05
798 0x38, PATCH_UINT16(0x038e), // pushi 910d
799 0x33, 0x03, // jmp 03
800 0x38, PATCH_UINT16(0x0390), // pushi 912d
801 0x81, 0x7d, // lag 7d
802 0x78, // push1
803 0x1c, // ne? [ sword-command != 1 ]
804 0x36, // push
805 0x43, 0x02, 0x04, // callk ScriptID 04 [ ScriptID (sword-command <= 2) ? 910 : 912, (sword-command != 1) ]
806 0x36, // push
807 0x81, 0x00, // lag 00
808 0x4a, 0x06, // send 06 [ ego setScript: sword-script ]
809 0x48, // ret
810 PATCH_ADDTOOFFSET(+89),
811 0x48, // ret [ remove toss since dup instructions were removed ]
812 PATCH_END
813 };
814
815 // When Arthur's sword is drawn, ARTHUR:doit calls kGetEvent a second time on
816 // each cycle to test if a shift key is pressed, causing input events to be
817 // frequently dropped. This is similar to Freddy Pharkas and QFG1VGA where this
818 // technique just happened to usually work in Sierra's interpreter. We fix this
819 // in the same way by using the current event instead of consuming a new one.
820 //
821 // Applies to: All versions
822 // Responsible method: ARTHUR:doit
823 // Fixes bug: #11269
824 static const uint16 camelotSignatureSwordEvents[] = {
825 0x30, SIG_MAGICDWORD, // bnt 0045
826 SIG_UINT16(0x0045),
827 0x39, SIG_SELECTOR8(new), // pushi new
828 0x76, // push0
829 0x51, 0x07, // class Event
830 0x4a, 0x04, // send 04 [ Event new: ]
831 0xa5, 0x01, // sat 01 [ temp1 = Event new: ]
832 SIG_ADDTOOFFSET(+53),
833 0x39, SIG_SELECTOR8(dispose), // pushi dispose
834 0x76, // push0
835 0x85, 0x01, // lat 01
836 0x4a, 0x04, // send 04 [ temp1 dispose: ]
837 SIG_END
838 };
839
840 static const uint16 camelotPatchSwordEvents[] = {
841 0x31, 0x46, // bnt 46
842 0x38, PATCH_SELECTOR16(curEvent), // pushi curEvent
843 0x76, // push0
844 0x51, 0x30, // class User [ User: curEvent ]
845 PATCH_ADDTOOFFSET(+57),
846 0x33, 0x05, // jmp 05 [ don't dispose event ]
847 PATCH_END
848 };
849
850 // script, description, signature patch
851 static const SciScriptPatcherEntry camelotSignatures[] = {
852 { true, 0, "fix sword sheathing", 1, camelotSignatureSwordSheathing, camelotPatchSwordSheathing },
853 { true, 0, "fix sword events", 1, camelotSignatureSwordEvents, camelotPatchSwordEvents },
854 { true, 11, "fix hunter missing points", 1, camelotSignatureHunterMissingPoints, camelotPatchHunterMissingPoints },
855 { true, 62, "fix peepingTom Sierra bug", 1, camelotSignaturePeepingTom, camelotPatchPeepingTom },
856 { true, 64, "fix Fatima room messages", 2, camelotSignatureFatimaRoomMessages, camelotPatchFatimaRoomMessages },
857 { true, 158, "fix give mule message", 1, camelotSignatureGiveMuleMessage, camelotPatchGiveMuleMessage },
858 { true, 169, "fix relic merchant lockup (1/2)", 1, camelotSignatureRelicMerchantLockup1, camelotPatchRelicMerchantLockup1 },
859 { true, 169, "fix relic merchant lockup (2/2)", 1, camelotSignatureRelicMerchantLockup2, camelotPatchRelicMerchantLockup2 },
860 SCI_SIGNATUREENTRY_TERMINATOR
861 };
862
863 // ===========================================================================
864 // stayAndHelp::changeState (0) is called when ego swims to the left or right
865 // boundaries of room 660. Normally a textbox is supposed to get on screen
866 // but the call is wrong, so not only do we get an error message the script
867 // is also hanging because the cue won't get sent out
868 // This also happens in sierra sci
869 // Applies to at least: PC-CD
870 // Responsible method: stayAndHelp::changeState
871 // Fixes bug: #5107
872 static const uint16 ecoquest1SignatureStayAndHelp[] = {
873 0x3f, 0x01, // link 01
874 0x87, 0x01, // lap param[1]
875 0x65, 0x14, // aTop state
876 0x36, // push
877 0x3c, // dup
878 0x35, 0x00, // ldi 00
879 0x1a, // eq?
880 0x31, 0x1c, // bnt [next state]
881 0x76, // push0
882 0x45, 0x01, 0x00, // callb [export 1 of script 0], 00 (switching control off)
883 SIG_MAGICDWORD,
884 0x38, SIG_UINT16(0x0122), // pushi 0122
885 0x78, // push1
886 0x76, // push0
887 0x81, 0x00, // lag global[0]
888 0x4a, 0x06, // send 06 - call ego::setMotion(0)
889 0x39, SIG_SELECTOR8(init), // pushi init
890 0x39, 0x04, // pushi 04
891 0x76, // push0
892 0x76, // push0
893 0x39, 0x17, // pushi 17
894 0x7c, // pushSelf
895 0x51, 0x82, // class EcoNarrator
896 0x4a, 0x0c, // send 0c - call EcoNarrator::init(0, 0, 23, self) (BADLY BROKEN!)
897 0x33, // jmp [end]
898 SIG_END
899 };
900
901 static const uint16 ecoquest1PatchStayAndHelp[] = {
902 0x87, 0x01, // lap param[1]
903 0x65, 0x14, // aTop state
904 0x36, // push
905 0x2f, 0x22, // bt [next state] (this optimization saves 6 bytes)
906 0x39, 0x00, // pushi 0 (wasting 1 byte here)
907 0x45, 0x01, 0x00, // callb [export 1 of script 0], 00 (switching control off)
908 0x38, PATCH_UINT16(0x0122), // pushi 0122
909 0x78, // push1
910 0x76, // push0
911 0x81, 0x00, // lag global[0]
912 0x4a, 0x06, // send 06 - call ego::setMotion(0)
913 0x39, PATCH_SELECTOR8(init), // pushi init
914 0x39, 0x06, // pushi 06
915 0x39, 0x02, // pushi 02 (additional 2 bytes)
916 0x76, // push0
917 0x76, // push0
918 0x39, 0x17, // pushi 17
919 0x7c, // pushSelf
920 0x38, PATCH_UINT16(0x0280), // pushi 280 (additional 3 bytes)
921 0x51, 0x82, // class EcoNarrator
922 0x4a, 0x10, // send 10 - call EcoNarrator::init(2, 0, 0, 23, self, 640)
923 PATCH_END
924 };
925
926 // Giving the oily shell to Superfluous when he's out of the mask runs the
927 // wrong animation and skips messages in the CD version. Sierra modified
928 // getInOilyShell for the CD version by adding a new state to the beginning
929 // but forgot to increment the state numbers passed to self:changeState to
930 // their new values, causing the script to change to the wrong states.
931 //
932 // We fix this by incrementing the state numbers passed to self:changeState.
933 //
934 // Applies to: PC CD
935 // Responsible method: getInOilyShell:changeState
936 // Fixes bug #10881
937 static const uint16 ecoquest1SignatureGiveOilyShell[] = {
938 0x30, SIG_UINT16(0x000a), // bnt 000a
939 0x38, SIG_UINT16(0x0090), // pushi changeState [ hard coded for CD ]
940 0x78, // push1
941 0x7a, // push2 [ state 2 ]
942 0x54, SIG_MAGICDWORD, 0x06, // self 06
943 0x32, SIG_UINT16(0x0195), // jmp 0195
944 SIG_ADDTOOFFSET(+209),
945 0x39, 0x08, // pushi 08 [ state 8 ]
946 SIG_ADDTOOFFSET(+16),
947 0x39, 0x0b, // pushi 0b [ state 11 ]
948 SIG_END
949 };
950
951 static const uint16 ecoquest1PatchGiveOilyShell[] = {
952 0x31, 0x0b, // bnt 0b
953 0x38, PATCH_UINT16(0x0090), // pushi changeState [ hard coded for CD ]
954 0x78, // push1
955 0x39, 0x03, // pushi 03 [ state 3 ]
956 PATCH_ADDTOOFFSET(+214),
957 0x39, 0x09, // pushi 09 [ state 9 ]
958 PATCH_ADDTOOFFSET(+16),
959 0x39, 0x0c, // pushi 0c [ state 12 ]
960 PATCH_END
961 };
962
963 // Reading the prophecy scroll in the CD version breaks messages in at least
964 // rooms 100 and 120. scrollScript:init overwrites the global that holds the
965 // noun for the room's message tuples. This global was added in the CD version
966 // and is set by most rooms during initialization. This pattern was mistakenly
967 // applied to scrollScript which isn't a room and doesn't depend on the global.
968 //
969 // We fix this by skipping the problematic code which overwrites the global.
970 //
971 // Applies to: PC CD
972 // Responsible method: scrollScript:init
973 // Fixes bug #10883
974 static const uint16 ecoquest1SignatureProphecyScroll[] = {
975 SIG_MAGICDWORD,
976 0x35, 0x01, // ldi 01
977 0xa1, 0xfa, // sag fa [ global250 = 1 ]
978 SIG_END
979 };
980
981 static const uint16 ecoquest1PatchProphecyScroll[] = {
982 0x33, 0x02, // jmp 02 [ don't set global250 ]
983 PATCH_END
984 };
985
986 // The empty apartments have several broken messages in the CD version due to
987 // not setting the global that holds the current room's noun, so we set it.
988 //
989 // Applies to: PC CD
990 // Responsible method: rm220:init
991 // Fixes bug #10903
992 static const uint16 ecoquest1SignatureEmptyApartmentMessages[] = {
993 SIG_MAGICDWORD,
994 0x54, 0x0c, // self 0c [ self setRegions: 51, addObstacle: ... ]
995 0x39, SIG_SELECTOR8(init), // pushi init
996 0x76, // push0
997 0x59, 0x01, // &rest 01 [ unused by ApartmentRoom:init ]
998 0x57, 0x96, 0x04, // super ApartmentRoom 04 [ super init: &rest ]
999 SIG_END
1000 };
1001
1002 static const uint16 ecoquest1PatchEmptyApartmentMessages[] = {
1003 0x35, 0x01, // ldi 01 [ the room's noun ]
1004 PATCH_ADDTOOFFSET(+3),
1005 0xa1, 0xfa, // sag fa [ global250 = 1 ]
1006 0x57, 0x96, 0x10, // super ApartmentRoom 10 [ combine self and super ]
1007 PATCH_END
1008 };
1009
1010 // The temple has a complex script bug in the CD version which can crash the
1011 // interpreter when solving the mosaic puzzle after loading a game that was
1012 // saved during the puzzle. The bug causes invalid memory access which locks up
1013 // Sierra's interpreter and can cause ours to fail an assertion.
1014 //
1015 // Room 140 has three insets and a conch shell in the middle. This room's script
1016 // was significantly changed in the CD version and transition animations were
1017 // added. Due to these changes the shell no longer renders beneath the insets
1018 // and so Sierra added code to hide the shell while they're displayed.
1019 // Unfortunately this code is incorrect and leaves the game in a state that's
1020 // unsafe to save. The shell is removed from the cast when showing an inset and
1021 // then shell:init is called when hiding. This leaves shell:underBits pointing
1022 // to hunk memory while temporarily not a member of the cast. Hunk memory isn't
1023 // persisted in saved games but underBits' values are. SCI games handle this in
1024 // Game:replay by clearing the underBits of cast members when restoring. Saving
1025 // while the puzzle is displayed causes shell:underBits' stale hunk value to
1026 // survive restoring. Solving the puzzle adds the shell back to the cast via
1027 // init followed by a call to kAnimate that accesses the potentially stale
1028 // shell:underBits. If the hunk segment id upon restoring in ScummVM is the
1029 // same as when saved then this out of bounds access will fail an assertion.
1030 //
1031 // We fix this by fully disposing the shell when showing an inset so that its
1032 // resources are cleaned up and it's safe to save the game. In order to do this
1033 // without changing the animation effect we set shell's disposal flag and then
1034 // immediately call shell:delete. This is equivalent to shell:dispose but
1035 // prevents hiding the shell before the transition animation takes place.
1036 //
1037 // Applies to: PC CD
1038 // Responsible methods: MosaicWall:doVerb, localproc_2ab6 in script 140
1039 // Fixes bug #10884
1040 static const uint16 ecoquest1SignatureMosaicPuzzleFix[] = {
1041 0x36, // push [ conchShell:owner ]
1042 0x34, SIG_UINT16(0x008c), // ldi 008c [ room number ]
1043 0x1a, // eq? [ is conchShell owned by room 140? ]
1044 0x30, SIG_UINT16(0x000b), // bnt 000b [ no shell to hide ]
1045 SIG_MAGICDWORD,
1046 0x39, SIG_SELECTOR8(delete), // pushi delete
1047 0x78, // push1
1048 0x72, SIG_UINT16(0x056a), // lofsa shell
1049 0x36, // push
1050 0x81, 0x05, // lag 05
1051 0x4a, 0x06, // send 06 [ cast delete: shell ]
1052 SIG_END
1053 };
1054
1055 static const uint16 ecoquest1PatchMosaicPuzzleFix[] = {
1056 0x89, 0x0b, // lsg 0b [ current room number, saves 2 bytes ]
1057 0x1a, // eq? [ is conchShell owned by room 140? ]
1058 0x31, 0x0e, // bnt 0e [ no shell to hide, save a byte ]
1059 0x39, PATCH_SELECTOR8(signal), // pushi signal
1060 0x78, // push1
1061 0x38, PATCH_UINT16(0xc014), // pushi c014 [ kSignalDisposeMe | shell:signal ]
1062 0x39, PATCH_SELECTOR8(delete), // pushi delete
1063 0x76, // push0
1064 0x72, PATCH_UINT16(0x056a), // lofsa shell
1065 0x4a, 0x0a, // send 0a [ shell signal: c014, delete ]
1066 PATCH_END
1067 };
1068
1069 // The column puzzle in room 160 can be put in a state that can't be completed.
1070 // This is a bug in the original that affects all versions.
1071 //
1072 // The puzzle consists of nine columns that must be rotated to their correct
1073 // positions in the correct order. As each column is solved it is locked. When
1074 // leaving the room the puzzle state is saved to globals but this state is
1075 // insufficient to recreate the puzzle. The game saves column positions but not
1076 // lock states. Instead it infers lock states from positions when restoring but
1077 // this is inaccurate because columns can be put in their correct positions out
1078 // of order. If the player leaves the room while all columns are in their
1079 // correct positions but before the puzzle is solved then all columns will be
1080 // locked when returning and the game can't be completed.
1081 //
1082 // The proper solution would be to save and restore lock states but it would be
1083 // impractical to patch in that functionality while retaining save game
1084 // compatibility. Instead we patch the loop that reinitializes lock states to
1085 // skip the last column so that it's always unlocked and the player can't get
1086 // stuck. This code only runs when the puzzle isn't solved and should never
1087 // have been able to lock the last column.
1088 //
1089 // Applies to: All Floppy and CD versions
1090 // Responsible method: Local procedure 5 in script 160
1091 // Fixes bug #10885
1092 static const uint16 ecoquest1SignatureColumnPuzzleFix[] = {
1093 0x39, SIG_SELECTOR8(size), // pushi size
1094 0x76, // push0
1095 0x72, SIG_ADDTOOFFSET(+2), // lofsa columnList [ columns in solution order ]
1096 0x4a, 0x04, // send 04 [ columnList:size ]
1097 0x22, // lt? [ temp0 < columnList:size (9) ]
1098 0x30, SIG_ADDTOOFFSET(+2), // bnt [ end of method ]
1099 0x39, SIG_MAGICDWORD, // pushi cel
1100 SIG_SELECTOR8(cel),
1101 0x76, // push0
1102 0x39, SIG_SELECTOR8(at), // pushi at
1103 SIG_END
1104 };
1105
1106 static const uint16 ecoquest1PatchColumnPuzzleFix[] = {
1107 0x34, PATCH_UINT16(0x0008), // ldi 0008 [ only initialize 8 of 9 columns ]
1108 0x32, PATCH_UINT16(0x0002), // jmp 0002
1109 PATCH_END
1110 };
1111
1112 // The ocean cliffs that border rooms 320 and 321 aren't displayed in the CD
1113 // version. Instead they are drawn above the visible area and on more screens
1114 // than they should. This also occurs in the original.
1115 //
1116 // Cliff views 325 and 326 have y displacements greater than 127 in the floppy
1117 // versions. In the CD version these offsets were changed to zero. Sierra
1118 // attempted to compensate for this by adding rows of empty pixels to the views
1119 // but it appears that someone mistook the unsigned offsets for negative values
1120 // and added the wrong number of rows to the wrong side of the views, causing
1121 // the cliffs to be drawn 256 pixels higher than normal.
1122 //
1123 // The ocean scripts were changed to use different techniques for adding and
1124 // removing the cliffs but this introduced more errors. Room 321 reinitializes
1125 // the cliffs instead of disposing them, causing them to be redrawn on the
1126 // wrong screens, and room 320 disposes the eastern cliffs instead of western.
1127 //
1128 // We fix the cliffs by adjusting their positions by 256 and disposing of them
1129 // in room 321. We leave room 320's incorrect cliff disposal in place since
1130 // both are automatically disposed of when that room's pic changes.
1131 //
1132 // Applies to: PC CD
1133 // Responsible methods: Heap in scripts 320 and 321, toEast:changeState, toWest:changeState
1134 // Fixes bug #10893
1135 static const uint16 ecoquest1SignatureSouthCliffsPosition[] = {
1136 SIG_MAGICDWORD,
1137 SIG_UINT16(0x0095), // easternCliffs:x = 149
1138 SIG_UINT16(0x0033), // easternCliffs:y = 51
1139 SIG_ADDTOOFFSET(+88),
1140 SIG_UINT16(0x0004), // westernCliffs:x = 4
1141 SIG_UINT16(0x0014), // westernCliffs:y = 20
1142 SIG_END
1143 };
1144
1145 static const uint16 ecoquest1PatchSouthCliffsPosition[] = {
1146 PATCH_ADDTOOFFSET(+2),
1147 PATCH_UINT16(0x0133), // easternCliffs:y = 307
1148 PATCH_ADDTOOFFSET(+90),
1149 PATCH_UINT16(0x0114), // westernCliffs:y = 276
1150 PATCH_END
1151 };
1152
1153 static const uint16 ecoquest1SignatureNorthCliffsPosition[] = {
1154 SIG_MAGICDWORD,
1155 SIG_UINT16(0x00eb), // easternCliffs:x = 236
1156 SIG_UINT16(0x0038), // easternCliffs:y = 56
1157 SIG_ADDTOOFFSET(+88),
1158 SIG_UINT16(0x0000), // westernCliffs:x = 0
1159 SIG_UINT16(0x0032), // westernCliffs:y = 50
1160 SIG_END
1161 };
1162
1163 static const uint16 ecoquest1PatchNorthCliffsPosition[] = {
1164 PATCH_ADDTOOFFSET(+2),
1165 PATCH_UINT16(0x0138), // easternCliffs:y = 312
1166 PATCH_ADDTOOFFSET(+90),
1167 PATCH_UINT16(0x0132), // westernCliffs:y = 306
1168 PATCH_END
1169 };
1170
1171 static const uint16 ecoquest1SignatureNorthCliffsDisposal[] = {
1172 0x39, SIG_SELECTOR8(init), // pushi init
1173 0x76, // push0
1174 0x72, SIG_ADDTOOFFSET(+2), // lofsa easternCliffs or westernCliffs
1175 0x4a, SIG_MAGICDWORD, 0x04, // send 04 [ cliffs init: ]
1176 0x38, SIG_SELECTOR16(obstacles), // pushi obstacles
1177 SIG_END
1178 };
1179
1180 static const uint16 ecoquest1PatchNorthCliffsDisposal[] = {
1181 0x39, PATCH_SELECTOR8(dispose), // pushi dispose
1182 PATCH_END
1183 };
1184
1185 // The Spanish version of EcoQuest accidentally shipped with temporary test code
1186 // that breaks the game when entering Olympia's apartment. (room 226)
1187 //
1188 // A message box's position was localized in the Spanish version. This message
1189 // occurs after saving Olympia by pumping bleach out of the window. To test
1190 // this change, a developer added code to forcibly run the usePump script upon
1191 // entering the room, but then forgot to remove it. This breaks the puzzle and
1192 // locks up the game upon re-entering the room.
1193 //
1194 // We fix this by disabling the test code that should not have been shipped.
1195 //
1196 // Applies to: Spanish PC Floppy
1197 // Responsible method: rm226:init
1198 // Fixes bug #10900
1199 static const uint16 ecoquest1SignatureBleachPumpTest[] = {
1200 SIG_MAGICDWORD,
1201 0x78, // push1
1202 0x39, 0x35, // pushi 35
1203 0x46, SIG_UINT16(0x0333), // calle proc819_3 [ set recycled-bleach flag ]
1204 SIG_UINT16(0x0003), 0x02,
1205 0x38, SIG_SELECTOR16(setScript), // pushi setScript
1206 0x78, // push1
1207 0x72, SIG_UINT16(0x0a44), // lofsa usePump
1208 0x36, // push
1209 0x54, 0x06, // self 06 [ self setScript: usePump ]
1210 SIG_END
1211 };
1212
1213 static const uint16 ecoquest1PatchBleachPumpTest[] = {
1214 0x32, PATCH_UINT16(0x0010), // jmp 0010 [ skip test code ]
1215 PATCH_END
1216 };
1217
1218 // script, description, signature patch
1219 static const SciScriptPatcherEntry ecoquest1Signatures[] = {
1220 { true, 140, "CD: mosaic puzzle fix", 2, ecoquest1SignatureMosaicPuzzleFix, ecoquest1PatchMosaicPuzzleFix },
1221 { true, 160, "CD: give superfluous oily shell", 1, ecoquest1SignatureGiveOilyShell, ecoquest1PatchGiveOilyShell },
1222 { true, 160, "CD/Floppy: column puzzle fix", 1, ecoquest1SignatureColumnPuzzleFix, ecoquest1PatchColumnPuzzleFix },
1223 { true, 220, "CD: empty apartment messages", 1, ecoquest1SignatureEmptyApartmentMessages, ecoquest1PatchEmptyApartmentMessages },
1224 { true, 226, "Spanish: disable bleach pump test", 1, ecoquest1SignatureBleachPumpTest, ecoquest1PatchBleachPumpTest },
1225 { true, 320, "CD: south cliffs position", 1, ecoquest1SignatureSouthCliffsPosition, ecoquest1PatchSouthCliffsPosition },
1226 { true, 321, "CD: north cliffs position", 1, ecoquest1SignatureNorthCliffsPosition, ecoquest1PatchNorthCliffsPosition },
1227 { true, 321, "CD: north cliffs disposal", 2, ecoquest1SignatureNorthCliffsDisposal, ecoquest1PatchNorthCliffsDisposal },
1228 { true, 660, "CD: bad messagebox and freeze", 1, ecoquest1SignatureStayAndHelp, ecoquest1PatchStayAndHelp },
1229 { true, 816, "CD: prophecy scroll", 1, ecoquest1SignatureProphecyScroll, ecoquest1PatchProphecyScroll },
1230 SCI_SIGNATUREENTRY_TERMINATOR
1231 };
1232
1233 // ===========================================================================
1234 // doMyThing::changeState (2) is supposed to remove the initial text on the
1235 // ecorder. This is done by reusing temp-space, that was filled on state 1.
1236 // this worked in sierra sci just by accident. In our sci, the temp space
1237 // is resetted every time, which means the previous text isn't available
1238 // anymore. We have to patch the code because of that.
1239 // Fixes bug: #4993
1240 static const uint16 ecoquest2SignatureEcorder[] = {
1241 0x31, 0x22, // bnt [next state]
1242 0x39, 0x0a, // pushi 0a
1243 0x5b, 0x04, 0x1e, // lea temp[1e]
1244 0x36, // push
1245 SIG_MAGICDWORD,
1246 0x39, 0x64, // pushi 64
1247 0x39, 0x7d, // pushi 7d
1248 0x39, 0x32, // pushi 32
1249 0x39, 0x66, // pushi 66
1250 0x39, 0x17, // pushi 17
1251 0x39, 0x69, // pushi 69
1252 0x38, SIG_UINT16(0x2631), // pushi 2631
1253 0x39, 0x6a, // pushi 6a
1254 0x39, 0x64, // pushi 64
1255 0x43, 0x1b, 0x14, // callk Display
1256 0x35, 0x0a, // ldi 0a
1257 0x65, 0x20, // aTop ticks
1258 0x33, // jmp [end]
1259 SIG_ADDTOOFFSET(+1), // [skip 1 byte]
1260 0x3c, // dup
1261 0x35, 0x03, // ldi 03
1262 0x1a, // eq?
1263 0x31, // bnt [end]
1264 SIG_END
1265 };
1266
1267 static const uint16 ecoquest2PatchEcorder[] = {
1268 0x2f, 0x02, // bt [to pushi 7]
1269 0x3a, // toss
1270 0x48, // ret
1271 0x38, PATCH_UINT16(0x0007), // pushi 7d (parameter count) (waste 1 byte)
1272 0x39, 0x0b, // pushi 11d (FillBoxAny)
1273 0x39, 0x1d, // pushi 29d
1274 0x39, 0x73, // pushi 115d
1275 0x39, 0x5e, // pushi 94d
1276 0x38, PATCH_UINT16(0x00d7), // pushi 215d
1277 0x78, // push1 (visual screen)
1278 0x38, PATCH_UINT16(0x0017), // pushi 23d (color) (waste 1 byte)
1279 0x43, 0x6c, 0x0e, // callk Graph
1280 0x38, PATCH_UINT16(0x0005), // pushi 5d (parameter count) (waste 1 byte)
1281 0x39, 0x0c, // pushi 12d (UpdateBox)
1282 0x39, 0x1d, // pushi 29d
1283 0x39, 0x73, // pushi 115d
1284 0x39, 0x5e, // pushi 94d
1285 0x38, PATCH_UINT16(0x00d7), // pushi 215d
1286 0x43, 0x6c, 0x0a, // callk Graph
1287 PATCH_END
1288 };
1289
1290 // Same patch as above for the ecorder introduction.
1291 // Two workarounds are needed for this patch in workarounds.cpp (when calling
1292 // kGraphFillBoxAny and kGraphUpdateBox), as there isn't enough space to patch
1293 // the function otherwise.
1294 // Fixes bug: #6467
1295 static const uint16 ecoquest2SignatureEcorderTutorial[] = {
1296 0x30, SIG_UINT16(0x0023), // bnt [next state]
1297 0x39, 0x0a, // pushi 0a
1298 0x5b, 0x04, 0x1f, // lea temp[1f]
1299 0x36, // push
1300 SIG_MAGICDWORD,
1301 0x39, 0x64, // pushi 64
1302 0x39, 0x7d, // pushi 7d
1303 0x39, 0x32, // pushi 32
1304 0x39, 0x66, // pushi 66
1305 0x39, 0x17, // pushi 17
1306 0x39, 0x69, // pushi 69
1307 0x38, SIG_UINT16(0x2631), // pushi 2631
1308 0x39, 0x6a, // pushi 6a
1309 0x39, 0x64, // pushi 64
1310 0x43, 0x1b, 0x14, // callk Display
1311 0x35, 0x1e, // ldi 1e
1312 0x65, 0x20, // aTop ticks
1313 0x32, // jmp [end]
1314 // 2 extra bytes, jmp offset
1315 SIG_END
1316 };
1317
1318 static const uint16 ecoquest2PatchEcorderTutorial[] = {
1319 0x31, 0x23, // bnt [next state] (save 1 byte)
1320 // The parameter count below should be 7, but we're out of bytes
1321 // to patch! A workaround has been added because of this
1322 0x78, // push1 (parameter count)
1323 //0x39, 0x07, // pushi 7d (parameter count)
1324 0x39, 0x0b, // pushi 11d (FillBoxAny)
1325 0x39, 0x1d, // pushi 29d
1326 0x39, 0x73, // pushi 115d
1327 0x39, 0x5e, // pushi 94d
1328 0x38, PATCH_UINT16(0x00d7), // pushi 215d
1329 0x78, // push1 (visual screen)
1330 0x39, 0x17, // pushi 23d (color)
1331 0x43, 0x6c, 0x0e, // callk Graph
1332 // The parameter count below should be 5, but we're out of bytes
1333 // to patch! A workaround has been added because of this
1334 0x78, // push1 (parameter count)
1335 //0x39, 0x05, // pushi 5d (parameter count)
1336 0x39, 0x0c, // pushi 12d (UpdateBox)
1337 0x39, 0x1d, // pushi 29d
1338 0x39, 0x73, // pushi 115d
1339 0x39, 0x5e, // pushi 94d
1340 0x38, PATCH_UINT16(0x00d7), // pushi 215d
1341 0x43, 0x6c, 0x0a, // callk Graph
1342 // We are out of bytes to patch at this point,
1343 // so we skip 494 (0x1ee) bytes to reuse this code:
1344 // ldi 1e
1345 // aTop 20
1346 // jmp 030e (jump to end)
1347 0x32, PATCH_UINT16(0x01ee), // jmp 494d
1348 PATCH_END
1349 };
1350
1351 // Clicking an icon during the icon bar tutorial in room 100 sends messages to
1352 // an uninitialized temporary variable. This is supposed to be the dispatched
1353 // Event object that's passed around earlier in the call stack. In Sierra's
1354 // interpreter that's what happened to be at this location and it worked.
1355 //
1356 // We fix this by using the global variable that stores the current Event object
1357 // instead of the uninitialized temp variable that accidentally points to it.
1358 // User:handleEvent sets this global before dispatching an event.
1359 //
1360 // Applies to: All versions
1361 // Responsible methods: iconWalk:select, iconLook:select, ...
1362 // Fixes bug: #11081
1363 static const uint16 ecoquest2SignatureIconBarTutorial[] = {
1364 0x7a, // push2
1365 0x38, SIG_SELECTOR16(handleEvent), // pushi handleEvent
1366 SIG_MAGICDWORD,
1367 0x8d, 0x01, // lst 01 [ uninitialized ]
1368 0x4a, 0x08, // send 08 [ EventHandler firstTrue: handleEvent temp1 ]
1369 SIG_END
1370 };
1371
1372 static const uint16 ecoquest2PatchIconBarTutorial[] = {
1373 PATCH_ADDTOOFFSET(+4),
1374 0x89, 0x18, // lsg 18 [ current event ]
1375 PATCH_END
1376 };
1377
1378 // The electronic organizer and password paper reappear in room 500 after they
1379 // fall into the water when entering the canoe. rm500:init only tests if these
1380 // items are in inventory. It should have also tested the canoe flag like room
1381 // 530 does to prevent the vacuum from reappearing.
1382 //
1383 // We fix this by only adding an item to the room if its InvI:owner is zero.
1384 // This is initially zero, then set to ego when getting an item, and finally
1385 // set to negative one when the item is removed from inventory.
1386 //
1387 // Applies to: All versions
1388 // Responsible method: rm500:init
1389 // Fixes bug: #11135
1390 static const uint16 ecoquest2SignatureRoom500Items[] = {
1391 0x38, SIG_ADDTOOFFSET(+2), // pushi test
1392 0x78, // push1
1393 SIG_MAGICDWORD,
1394 0x39, 0x0b, // pushi 0b
1395 0x81, 0x96, // lag 96
1396 0x4a, 0x06, // send 06 [ cibolaFlags test: 11 ]
1397 0xa5, 0x00, // sat 00
1398 0x38, SIG_ADDTOOFFSET(+2), // pushi test
1399 0x78, // push1
1400 0x39, 0x04, // pushi 04
1401 0x81, 0x96, // lag 96
1402 0x4a, 0x06, // send 06 [ cibolaFlags test: 4 ]
1403 0xa5, 0x01, // sat 01
1404 0x38, SIG_ADDTOOFFSET(+2), // pushi test
1405 0x78, // push1
1406 0x39, 0x17, // pushi 17
1407 0x81, 0x96, // lag 96
1408 0x4a, 0x06, // send 06 [ cibolaFlags test: 23 ]
1409 0xa5, 0x02, // sat 02
1410 0x38, SIG_ADDTOOFFSET(+2), // pushi test
1411 SIG_ADDTOOFFSET(+636),
1412 0x38, SIG_SELECTOR16(has), // pushi has
1413 0x78, // push1
1414 0x39, 0x15, // pushi 15
1415 0x81, 0x00, // lag 00
1416 0x4a, 0x06, // send 06 [ ego has: 21 ]
1417 0x18, // not
1418 0x31, 0x13, // bnt 13 [ don't initialize theOrganizer ]
1419 SIG_ADDTOOFFSET(+236),
1420 0x38, SIG_SELECTOR16(has), // pushi has
1421 0x78, // push1
1422 0x39, 0x0b, // pushi 0b
1423 0x81, 0x00, // lag 00
1424 0x4a, 0x06, // send 06 [ ego has: 11 ]
1425 0x18, // not
1426 0x30, SIG_UINT16(0x0058), // bnt 0058 [ don't initialize paper ]
1427 SIG_END
1428 };
1429
1430 static const uint16 ecoquest2PatchRoom500Items[] = {
1431 0x39, PATCH_SELECTOR8(at), // pushi at
1432 0x3c, // dup [ push at, saves 1 byte ]
1433 0x78, // push1
1434 0x39, 0x15, // pushi 15
1435 0x38, PATCH_GETORIGINALUINT16(+1), // pushi test
1436 0x3c, // dup [ push test, saves 2 bytes ]
1437 0x3c, // dup [ push test, saves 2 bytes ]
1438 0x3c, // dup [ push test, saves 2 bytes ]
1439 0x78, // push1
1440 0x39, 0x0b, // pushi 0b
1441 0x81, 0x96, // lag 96
1442 0x4a, 0x06, // send 06 [ cibolaFlags test: 11 ]
1443 0xa5, 0x00, // sat 00
1444 0x78, // push1
1445 0x39, 0x04, // pushi 04
1446 0x81, 0x96, // lag 96
1447 0x4a, 0x06, // send 06 [ cibolaFlags test: 4 ]
1448 0xa5, 0x01, // sat 01
1449 0x78, // push1
1450 0x39, 0x17, // pushi 17
1451 0x81, 0x96, // lag 96
1452 0x4a, 0x06, // send 06 [ cibolaFlags test: 23 ]
1453 0xa5, 0x02, // sat 02
1454 PATCH_ADDTOOFFSET(+636),
1455 0x81, 0x09, // lag 09
1456 0x4a, 0x06, // send 06 [ Inv at: 21 ]
1457 0x38, PATCH_SELECTOR16(owner), // pushi owner
1458 0x76, // push0
1459 0x4a, 0x04, // send 04 [ organizer owner? ]
1460 0x78, // push1
1461 0x2f, 0x13, // bt 13 [ don't initialize theOrganizer ]
1462 PATCH_ADDTOOFFSET(+236),
1463 0x39, 0x0b, // pushi 0b
1464 0x81, 0x09, // lag 09
1465 0x4a, 0x06, // send 06 [ Inv at: 11 ]
1466 0x38, PATCH_SELECTOR16(owner), // pushi owner
1467 0x76, // push0
1468 0x4a, 0x04, // send 04 [ password owner? ]
1469 0x2f, 0x58, // bt 58 [ don't initialize paper ]
1470 PATCH_END
1471 };
1472
1473 // The Ecorder cursor only highlights over one of the four Victoria lilies, even
1474 // though they all respond to Ecorder clicks. Each lily has a doit method that
1475 // highlights the cursor but three of them never execute because they are
1476 // added to the room pic. This removes them from the cast and prevents doit.
1477 //
1478 // We fix this by removing the addToPic calls so the lilies remain in the cast.
1479 //
1480 // Applies to: All versions
1481 // Responsible methods: lilly1:init, lilly2:init, lilly3:init
1482 // Fixes bug: #5552
1483 static const uint16 ecoquest2SignatureEcorderLily[] = {
1484 0x38, SIG_MAGICDWORD, // pushi addToPic
1485 SIG_SELECTOR16(addToPic),
1486 0x76, // push0
1487 0x54, 0x04, // self 04 [ self addToPic: ]
1488 SIG_END
1489 };
1490
1491 static const uint16 ecoquest2PatchEcorderLily[] = {
1492 0x32, PATCH_UINT16(0x0003), // jmp 0003
1493 PATCH_END
1494 };
1495
1496 // script, description, signature patch
1497 static const SciScriptPatcherEntry ecoquest2Signatures[] = {
1498 { true, 0, "icon bar tutorial", 10, ecoquest2SignatureIconBarTutorial, ecoquest2PatchIconBarTutorial },
1499 { true, 50, "initial text not removed on ecorder", 1, ecoquest2SignatureEcorder, ecoquest2PatchEcorder },
1500 { true, 333, "initial text not removed on ecorder tutorial", 1, ecoquest2SignatureEcorderTutorial, ecoquest2PatchEcorderTutorial },
1501 { true, 500, "room 500 items reappear", 1, ecoquest2SignatureRoom500Items, ecoquest2PatchRoom500Items },
1502 { true, 702, "ecorder not highlighting lilies", 3, ecoquest2SignatureEcorderLily, ecoquest2PatchEcorderLily },
1503 SCI_SIGNATUREENTRY_TERMINATOR
1504 };
1505
1506 // ===========================================================================
1507 // Fan-made games
1508 // Attention: Try to make script patches as specific as possible
1509
1510 // CascadeQuest::autosave in script 994 is called various times to auto-save.
1511 // It uses a fixed slot (999) for this purpose. This doesn't work in ScummVM,
1512 // because we do not let scripts save directly into specific slots, but
1513 // instead use virtual slots / detect scripts wanting to create a new slot.
1514 // We patch the code to use slot 99 instead. kSaveGame also checks for Cascade
1515 // Quest, and if slot 99 is asked for, it will then use the actual slot 0,
1516 // which is the official ScummVM auto-save slot.
1517 //
1518 // Responsible method: CascadeQuest::autosave
1519 // Fixes bug: #7007
1520 static const uint16 fanmadeSignatureCascadeQuestFixAutoSaving[] = {
1521 SIG_MAGICDWORD,
1522 0x38, SIG_UINT16(0x03e7), // pushi 3E7 (999d) -> save game slot 999
1523 0x74, SIG_UINT16(0x06f8), // lofss "AutoSave"
1524 0x89, 0x1e, // lsg global[1e]
1525 0x43, 0x2d, 0x08, // callk SaveGame
1526 SIG_END
1527 };
1528
1529 static const uint16 fanmadePatchCascadeQuestFixAutoSaving[] = {
1530 0x38, PATCH_UINT16((SAVEGAMEID_OFFICIALRANGE_START - 1)), // fix slot
1531 PATCH_END
1532 };
1533
1534 // EventHandler::handleEvent in Demo Quest has a bug, and it jumps to the
1535 // wrong address when an incorrect word is typed, therefore leading to an
1536 // infinite loop. This script bug was not apparent in SSCI, probably because
1537 // event handling was slightly different there, so it was never discovered.
1538 // Fixes bug: #5120
1539 static const uint16 fanmadeSignatureDemoQuestInfiniteLoop[] = {
1540 0x38, SIG_UINT16(0x004c), // pushi 004c
1541 0x39, 0x00, // pushi 00
1542 0x87, 0x01, // lap param[1]
1543 0x4b, 0x04, // send 04
1544 SIG_MAGICDWORD,
1545 0x18, // not
1546 0x30, SIG_UINT16(0x002f), // bnt 002f [06a5] --> jmp ffbc [0664] --> BUG! infinite loop
1547 SIG_END
1548 };
1549
1550 static const uint16 fanmadePatchDemoQuestInfiniteLoop[] = {
1551 PATCH_ADDTOOFFSET(+10),
1552 0x30, PATCH_UINT16(0x0032), // bnt 0032 [06a8] --> pushi 004c
1553 PATCH_END
1554 };
1555
1556 // script, description, signature patch
1557 static const SciScriptPatcherEntry fanmadeSignatures[] = {
1558 { true, 994, "Cascade Quest: fix auto-saving", 1, fanmadeSignatureCascadeQuestFixAutoSaving, fanmadePatchCascadeQuestFixAutoSaving },
1559 { true, 999, "Demo Quest: infinite loop on typo", 1, fanmadeSignatureDemoQuestInfiniteLoop, fanmadePatchDemoQuestInfiniteLoop },
1560 SCI_SIGNATUREENTRY_TERMINATOR
1561 };
1562
1563 // ===========================================================================
1564
1565 // WORKAROUND
1566 // Freddy Pharkas intro screen
1567 // Sierra used inner loops for the scaling of the 2 title views.
1568 // Those inner loops don't call kGameIsRestarting, which is why
1569 // we do not update the screen and we also do not throttle.
1570 //
1571 // This patch fixes this and makes it work.
1572 // Applies to at least: English PC-CD
1573 // Responsible method: sTownScript::changeState(1), sTownScript::changeState(3) (script 110)
1574 static const uint16 freddypharkasSignatureIntroScaling[] = {
1575 0x38, SIG_ADDTOOFFSET(+2), // pushi (setLoop) (009b for PC CD)
1576 0x78, // push1
1577 SIG_ADDTOOFFSET(+1), // push0 for first code, push1 for second code
1578 0x38, SIG_ADDTOOFFSET(+2), // pushi (setStep) (0143 for PC CD)
1579 0x7a, // push2
1580 0x39, 0x05, // pushi 05
1581 0x3c, // dup
1582 0x72, SIG_ADDTOOFFSET(+2), // lofsa (view)
1583 SIG_MAGICDWORD,
1584 0x4a, 0x1e, // send 1e
1585 0x35, 0x0a, // ldi 0a
1586 0xa3, 0x02, // sal local[2]
1587 // start of inner loop
1588 0x8b, 0x02, // lsl local[2]
1589 SIG_ADDTOOFFSET(+43), // skip almost all of inner loop
1590 0xa3, 0x02, // sal local[2]
1591 0x33, 0xcf, // jmp [inner loop start]
1592 SIG_END
1593 };
1594
1595 static const uint16 freddypharkasPatchIntroScaling[] = {
1596 // remove setLoop(), objects in heap are already prepared, saves 5 bytes
1597 0x38,
1598 PATCH_GETORIGINALUINT16(+6), // pushi (setStep)
1599 0x7a, // push2
1600 0x39, 0x05, // pushi 05
1601 0x3c, // dup
1602 0x72,
1603 PATCH_GETORIGINALUINT16(+13), // lofsa (view)
1604 0x4a, 0x18, // send 18 - adjusted
1605 0x35, 0x0a, // ldi 0a
1606 0xa3, 0x02, // sal local[2]
1607 // start of new inner loop
1608 0x39, 0x00, // pushi 00
1609 0x43, 0x2c, 0x00, // callk GameIsRestarting (add this to trigger our speed throttler)
1610 PATCH_ADDTOOFFSET(+47), // skip almost all of inner loop
1611 0x33, 0xca, // jmp [inner loop start]
1612 PATCH_END
1613 };
1614
1615 // PointsSound::check waits for a signal. If no signal is received, it'll call
1616 // kDoSound(0x0d) which is a dummy in sierra sci. ScummVM and will use acc
1617 // (which is not set by the dummy) to trigger sound disposal. This somewhat
1618 // worked in sierra sci because the sample was already playing in the sound
1619 // driver. In our case, that would also stop the sample from playing, so we
1620 // patch it out. The "score" code is already buggy and sets volume to 0 when
1621 // playing.
1622 // Applies to at least: English PC-CD
1623 // Responsible method: PointsSound::check in script 0
1624 // Fixes bug: #5059
1625 static const uint16 freddypharkasSignatureScoreDisposal[] = {
1626 0x67, 0x32, // pTos 32 (selector theAudCount)
1627 0x78, // push1
1628 SIG_MAGICDWORD,
1629 0x39, 0x0d, // pushi 0d
1630 0x43, 0x75, 0x02, // callk DoAudio
1631 0x1c, // ne?
1632 0x31, // bnt [skip disposal]
1633 SIG_END
1634 };
1635
1636 static const uint16 freddypharkasPatchScoreDisposal[] = {
1637 0x34, PATCH_UINT16(0x0000), // ldi 0000
1638 0x34, PATCH_UINT16(0x0000), // ldi 0000
1639 0x34, PATCH_UINT16(0x0000), // ldi 0000
1640 PATCH_END
1641 };
1642
1643 // In script 235, rm235::init and sEnterFrom500 disable icon 7+8 of iconbar (CD
1644 // only). When picking up the canister after placing it down, the scripts will
1645 // disable all the other icons. This results in IconBar::disable doing endless
1646 // loops even in sierra sci because there is no enabled icon left. We remove
1647 // the disabling of icon 8 (which is help). This fixes the issue.
1648 // Applies to at least: English PC-CD
1649 // Responsible method: rm235::init and sEnterFrom500::changeState
1650 // Fixes bug: #5245
1651 static const uint16 freddypharkasSignatureCanisterHang[] = {
1652 0x38, SIG_SELECTOR16(disable), // pushi disable
1653 0x7a, // push2
1654 SIG_MAGICDWORD,
1655 0x39, 0x07, // pushi 07
1656 0x39, 0x08, // pushi 08
1657 0x81, 0x45, // lag global[45]
1658 0x4a, 0x08, // send 08 (call IconBar::disable(7, 8))
1659 SIG_END
1660 };
1661
1662 static const uint16 freddypharkasPatchCanisterHang[] = {
1663 PATCH_ADDTOOFFSET(+3),
1664 0x78, // push1
1665 PATCH_ADDTOOFFSET(+2),
1666 0x33, 0x00, // jmp 0 (waste 2 bytes)
1667 PATCH_ADDTOOFFSET(+3),
1668 0x06, // send 06 (call IconBar::disable(7))
1669 PATCH_END
1670 };
1671
1672 // In script 215, lowerLadder::doit and highLadder::doit actually process
1673 // keyboard presses when the ladder is on the screen in that room. They
1674 // strangely also call kGetEvent. Because the main User::doit also calls
1675 // kGetEvent, it's pure luck, where the event will hit. It's the same issue
1676 // as in QfG1VGA. If you turn DOSBox to max cycles and click around for ego,
1677 // sometimes clicks also won't get registered. Strangely it's not nearly
1678 // as bad as in our sci, but these differences may be caused by timing.
1679 // We just reuse the active event, thus removing the duplicate kGetEvent
1680 // call.
1681 // Applies to at least: English PC-CD, German Floppy, English Mac
1682 // Responsible method: lowerLadder::doit and highLadder::doit
1683 // Fixes bug: #5060
1684 static const uint16 freddypharkasSignatureLadderEvent[] = {
1685 0x39, SIG_MAGICDWORD,
1686 SIG_SELECTOR8(new), // pushi new
1687 0x76, // push0
1688 0x38, SIG_SELECTOR16(curEvent), // pushi curEvent
1689 0x76, // push0
1690 0x81, 0x50, // lag global[50]
1691 0x4a, 0x04, // send 04 - read User::curEvent
1692 0x4a, 0x04, // send 04 - call curEvent::new
1693 0xa5, 0x00, // sat temp[0]
1694 0x38, SIG_SELECTOR16(localize), // pushi localize
1695 0x76, // push0
1696 0x4a, 0x04, // send 04 (call curEvent::localize)
1697 SIG_END
1698 };
1699
1700 static const uint16 freddypharkasPatchLadderEvent[] = {
1701 0x34, 0x00, 0x00, // ldi 0000 (waste 3 bytes, overwrites first 2 pushes)
1702 PATCH_ADDTOOFFSET(+8),
1703 0xa5, 0x00, // sat temp[0] (waste 2 bytes, overwrites 2nd send)
1704 PATCH_ADDTOOFFSET(+2),
1705 0x34, 0x00, 0x00, // ldi 0000
1706 0x34, 0x00, 0x00, // ldi 0000 (waste 6 bytes, overwrites last 3 opcodes)
1707 PATCH_END
1708 };
1709
1710 // In the Macintosh version of Freddy Pharkas, kRespondsTo is broken for
1711 // property selectors. They hacked the script to work around the issue,
1712 // so we revert the script back to using the values of the DOS script.
1713 // Applies to at least: English Mac
1714 // Responsible method: publicfpInv::drawInvWindow in script 15
1715 static const uint16 freddypharkasSignatureMacInventory[] = {
1716 SIG_MAGICDWORD,
1717 0x39, 0x23, // pushi 23
1718 0x39, 0x74, // pushi 74
1719 0x78, // push1
1720 0x38, SIG_UINT16(0x0174), // pushi 0174 (on mac it's actually 0x01, 0x74)
1721 0x85, 0x15, // lat temp[15]
1722 SIG_END
1723 };
1724
1725 static const uint16 freddypharkasPatchMacInventory[] = {
1726 0x39, 0x02, // pushi 02 (now matches the DOS version)
1727 PATCH_ADDTOOFFSET(+23),
1728 0x39, 0x04, // pushi 04 (now matches the DOS version)
1729 PATCH_END
1730 };
1731
1732 // WORKAROUND
1733 // FPFP Mac has an easter egg with a script bug that accidentally works in
1734 // Sierra's interpreter. Clicking Talk on a small part of the mine in room 270
1735 // triggers it. The script macThing plays macSound and waits for it to finish,
1736 // but macSound:loop is set to -1, indicating that it should loop forever.
1737 // ScummVM loops the sound and so macThing never advances to the next state and
1738 // the user never regains control. Sierra's interpreter cues the script after
1739 // the first play and doesn't loop the sound, despite macSound:loop.
1740 //
1741 // We work around this by setting macSound:loop correctly on the heap so that it
1742 // only plays once and macThing proceeds.
1743 //
1744 // Applies to: Mac Floppy
1745 // Responsible method: Heap in script 270
1746 // Fixes bug #7065
1747 static const uint16 freddypharkasSignatureMacEasterEgg[] = {
1748 SIG_MAGICDWORD, // macSound
1749 SIG_UINT16(0x0b89), // number = 2953
1750 SIG_UINT16(0x007f), // vol = 127
1751 SIG_UINT16(0x0000), // priority = 0
1752 SIG_UINT16(0xffff), // loop = -1 [ loop sound forever ]
1753 SIG_END
1754 };
1755
1756 static const uint16 freddypharkasPatchMacEasterEgg[] = {
1757 PATCH_ADDTOOFFSET(+6),
1758 PATCH_UINT16(0x0001), // loop = 1 [ play sound once ]
1759 PATCH_END
1760 };
1761
1762 // FPFP Mac is missing view 844 of Hop Singh leaving town, breaking the scene.
1763 // This occurs when going to the desert (room 200) after the restaurant closes
1764 // but before act 3 ends. This would also crash the original so we just disable
1765 // this minor optional scene.
1766 //
1767 // Applies to: Mac Floppy
1768 // Responsible method: rm200:init
1769 // Fixes bug #10954
1770 static const uint16 freddypharkasSignatureMacHopSingh[] = {
1771 0x89, 0x77, // lsg 77
1772 0x35, 0x13, // ldi 13
1773 0x1a, // eq? [ did restaurant just close? ]
1774 0x31, 0x46, // bnt 46 [ skip hop singh scene ]
1775 SIG_ADDTOOFFSET(+0x41),
1776 SIG_MAGICDWORD,
1777 0x72, 0x01, 0xd0, // lofsa hopSingh [ hard-coded big endian for mac ]
1778 0x4a, 0x20, // send 20 [ hopSingh init: ... setScript: sLeaveTown ]
1779 SIG_END
1780 };
1781
1782 static const uint16 freddypharkasPatchMacHopSingh[] = {
1783 0x33, 0x4b, // jmp 4b [ always skip hop singh scene ]
1784 PATCH_END
1785 };
1786
1787 // At the start of act 4 the church key is removed from inventory but reappears
1788 // in the church door. The door script attempts to prevent this by not drawing
1789 // the key in act 4 but the verb handler is missing this check. Looking at the
1790 // door in act 4 still brings up the inset with the key. Sierra fixed this in
1791 // Mac but forgot to include the fix in the CD version a year later.
1792 //
1793 // We fix this by replacing a duplicate inventory check with an act 4 check so
1794 // that the key no longer appears in the inset and can't be picked up again.
1795 //
1796 // Applies to: PC Floppy, PC CD
1797 // Responsible method: inDoorInset:init
1798 // Fixes bug #10975
1799 static const uint16 freddypharkasSignatureChurchKey[] = {
1800 SIG_MAGICDWORD,
1801 0x76, // push0
1802 0x59, 0x01, // &rest 01
1803 0x57, SIG_ADDTOOFFSET(+1), 0x04,// super Inset 04 [ super: init &rest ]
1804 0x38, SIG_SELECTOR16(has), // pushi has
1805 0x78, // push1
1806 0x39, 0x06, // pushi 06
1807 0x81, 0x00, // lag 00
1808 0x4a, 0x06, // send 06 [ ego has: 6 (church key) ]
1809 SIG_END
1810 };
1811
1812 static const uint16 freddypharkasPatchChurchKey[] = {
1813 PATCH_ADDTOOFFSET(+6),
1814 0x89, 0x78, // lsg 78 [ act number ]
1815 0x35, 0x04, // ldi 04
1816 0x20, // ge?
1817 0x33, 0x03, // jmp 03
1818 PATCH_END
1819 };
1820
1821 // After leaving the desk letter in the grave, the letter reappears in the desk.
1822 // The desk script only checks if the letter is in inventory. Sierra started to
1823 // fix this in the CD version by setting a new flag but forgot to check it.
1824 //
1825 // We fix this by testing Letter's owner, if -1 then it is in the grave.
1826 //
1827 // Applies to: All versions
1828 // Responsible method: deskDrawer:doVerb(1)
1829 // Fixes bug #10975
1830 static const uint16 freddypharkasSignatureDeskLetter[] = {
1831 SIG_MAGICDWORD,
1832 0x30, SIG_UINT16(0x0055), // bnt 0055
1833 0x38, SIG_SELECTOR16(has), // pushi has
1834 0x78, // push1
1835 0x39, 0x1f, // pushi 1f
1836 0x81, 0x00, // lag 00
1837 0x4a, 0x06, // send 06 [ ego has: 31 (Letter) ]
1838 0x18, // not
1839 0x31, 0x1f, // bnt 1f
1840 0x78, // push1
1841 0x39, 0x31, // pushi 31
1842 0x45, 0x02, 0x02, // callb proc0_2 02 [ is flag 49 set? ]
1843 0x31, 0x17, // bnt 17
1844 0x38, SIG_ADDTOOFFSET(+2), // pushi stopUpd
1845 0x76, // push0
1846 0x81, 0x00, // lag 00
1847 0x4a, 0x04, // send 04 [ ego stopUpd: (optimization) ]
1848 0x38, SIG_ADDTOOFFSET(+2), // pushi setInset
1849 0x78, // push1
1850 0x72, SIG_UINT16(0x0522), // lofsa inLetterInset
1851 0x36, // push
1852 0x81, 0x02, // lag 02
1853 0x4a, 0x06, // send 06 [ rm610 setInset: inLetterInset ]
1854 0x32, SIG_UINT16(0x008f), // jmp 008f [ end of method ]
1855 0x78, // push1
1856 0x39, 0x31, // pushi 31
1857 0x45, 0x02, 0x02, // callb proc0_2 02 [ is flag 49 set? ]
1858 0x31, 0x11, // bnt 11 [ drawer is closed ]
1859 SIG_END
1860 };
1861
1862 static const uint16 freddypharkasPatchDeskLetter[] = {
1863 0x31, 0x56, // bnt 56
1864 0x78, // push1
1865 0x39, 0x31, // pushi 31
1866 0x45, 0x02, 0x02, // callb proc0_2 02 [ is flag 49 set? ]
1867 0x31, 0x3e, // bnt 3e [ drawer is closed ]
1868 0x38, PATCH_SELECTOR16(has), // pushi has
1869 0x78, // push1
1870 0x39, 0x1f, // pushi 1f
1871 0x81, 0x00, // lag 00
1872 0x4a, 0x06, // send 06 [ ego has: 31 (Letter) ]
1873 0x2f, 0x21, // bt 21 [ drawer is open and empty ]
1874 0x39, PATCH_SELECTOR8(at), // pushi at
1875 0x78, // push1
1876 0x39, 0x1f, // pushi 1f
1877 0x81, 0x09, // lag 09
1878 0x4a, 0x06, // send 06 [ fpInv at: 31 (Letter) ]
1879 0x38, PATCH_SELECTOR16(owner), // pushi owner
1880 0x76, // push0
1881 0x4a, 0x04, // send 04 [ Letter owner? ]
1882 0x39, 0xff, // pushi ff
1883 0x1a, // eq?
1884 0x2f, 0x0d, // bt 0d [ drawer is open and empty ]
1885 0x38, PATCH_GETORIGINALUINT16(+33), // pushi setInset
1886 0x78, // push1
1887 0x74, PATCH_UINT16(0x0522), // lofss inLetterInset
1888 0x81, 0x02, // lag 02
1889 0x4a, 0x06, // send 06 [ rm610 setInset: inLetterInset ]
1890 0x3a, // toss
1891 0x48, // ret
1892 PATCH_END
1893 };
1894
1895 // script, description, signature patch
1896 static const SciScriptPatcherEntry freddypharkasSignatures[] = {
1897 { true, 0, "CD: score early disposal", 1, freddypharkasSignatureScoreDisposal, freddypharkasPatchScoreDisposal },
1898 { true, 15, "Mac: broken inventory", 1, freddypharkasSignatureMacInventory, freddypharkasPatchMacInventory },
1899 { true, 110, "intro scaling workaround", 2, freddypharkasSignatureIntroScaling, freddypharkasPatchIntroScaling },
1900 { false, 200, "Mac: skip broken hop singh scene", 1, freddypharkasSignatureMacHopSingh, freddypharkasPatchMacHopSingh },
1901 { true, 235, "CD: canister pickup hang", 3, freddypharkasSignatureCanisterHang, freddypharkasPatchCanisterHang },
1902 { true, 270, "Mac: easter egg hang", 1, freddypharkasSignatureMacEasterEgg, freddypharkasPatchMacEasterEgg },
1903 { true, 310, "church key reappears", 1, freddypharkasSignatureChurchKey, freddypharkasPatchChurchKey },
1904 { true, 320, "ladder event issue", 2, freddypharkasSignatureLadderEvent, freddypharkasPatchLadderEvent },
1905 { true, 610, "desk letter reappears", 1, freddypharkasSignatureDeskLetter, freddypharkasPatchDeskLetter },
1906 SCI_SIGNATUREENTRY_TERMINATOR
1907 };
1908
1909 // ===========================================================================
1910
1911 // During Bridge, Declarer_Second_NT:think performs a bitwise or against an
1912 // object due to a script typo. This operation is supposed to be against
1913 // (bridgeHand:highCard):rank but instead it's against bridgeHand:highCard.
1914 // ThirdSeat_Trump:think has a correct version of this. Declarer_Second_NT must
1915 // have just been missing the word "rank" in the original script.
1916 //
1917 // We fix this by inserting the missing rank code. To make room we remove the
1918 // call to self:checkFinCard. It's called immediately before this patch and its
1919 // result is stored in local1 so we just use that. Hoyle5 also has this bug.
1920 //
1921 // Applies to at least: English PC
1922 // Responsible method: Declarer_Second_NT:think
1923 // Fixes bug #11163
1924 static const uint16 hoyle4SignatureBridgeArithmetic[] = {
1925 0x36, // push [ bridgeHand:highCard ]
1926 0x34, SIG_UINT16(0x0f00), // ldi 0f00
1927 0x14, // or [ error: bridgeHand:highCard is an object ]
1928 0x36, // push
1929 0x63, 0x42, // pToa pard
1930 0x4a, 0x08, // send 08 [ pard hasCard: theSuitLead (bridgeHand:highCard | 0f00) ]
1931 SIG_MAGICDWORD,
1932 0x2f, 0x1d, // bt 1d
1933 0x83, 0x03, // lal 03
1934 0x2f, 0x19, // bt 19
1935 0x38, SIG_ADDTOOFFSET(+2), // pushi rank
1936 0x76, // push0
1937 0x38, SIG_ADDTOOFFSET(+2), // pushi checkFinCard
1938 0x78, // push1
1939 0x67, 0x48, // pTos theSuitLead
1940 0x54, 0x06, // self 06 [ self checkFinCard: theSuitLead ]
1941 SIG_END
1942 };
1943
1944 static const uint16 hoyle4PatchBridgeArithmetic[] = {
1945 0x38, PATCH_GETORIGINALUINT16(+17), // pushi rank
1946 0x76, // push0
1947 0x4a, 0x04, // send 04 [ bridgeHand:highCard rank? ]
1948 0x36, // push
1949 0x34, PATCH_UINT16(0x0f00), // ldi 0f00
1950 0x14, // or
1951 0x36, // push
1952 0x63, 0x42, // pToa pard
1953 0x4a, 0x08, // send 08 [ pard hasCard: theSuitLead ((bridgeHand:highCard):rank | 0f00) ]
1954 0x2f, 0x17, // bt 17
1955 0x83, 0x03, // lal 03
1956 0x2f, 0x13, // bt 13
1957 0x38, PATCH_GETORIGINALUINT16(+17), // pushi rank
1958 0x76, // push0
1959 0x83, 0x01, // lal 01 [ set to "self checkFinCard: theSuitLead" earlier ]
1960 PATCH_END
1961 };
1962
1963 // script, description, signature patch
1964 static const SciScriptPatcherEntry hoyle4Signatures[] = {
1965 { true, 733, "bridge arithmetic against object ", 1, hoyle4SignatureBridgeArithmetic, hoyle4PatchBridgeArithmetic },
1966 SCI_SIGNATUREENTRY_TERMINATOR
1967 };
1968
1969 #ifdef ENABLE_SCI32
1970 #pragma mark -
1971 #pragma mark Hoyle 5
1972
1973 // Several scripts in Hoyle5 contain a subroutine which spins on kGetTime until
1974 // a certain number of ticks elapse. Since this wastes CPU and makes ScummVM
1975 // unresponsive, the kWait kernel function (which was removed in SCI2) is
1976 // reintroduced for Hoyle5, and the spin subroutines are patched here to call
1977 // that function instead.
1978 // Applies to at least: English Demo
1979 static const uint16 hoyle5SignatureSpinLoop[] = {
1980 SIG_MAGICDWORD,
1981 0x76, // push0
1982 0x43, 0x79, SIG_UINT16(0x00), // callk GetTime, $0
1983 0x36, // push
1984 0x87, 0x01, // lap param[1]
1985 0x02, // add
1986 0xa5, 0x00, // sat temp[0]
1987 SIG_END
1988 };
1989
1990 static const uint16 hoyle5PatchSpinLoop[] = {
1991 0x78, // push1
1992 0x8f, 0x01, // lsp param[1]
1993 0x43, kScummVMWaitId, PATCH_UINT16(0x02), // callk Wait, $2
1994 0x48, // ret
1995 PATCH_END
1996 };
1997
1998 // While playing Old Maid (room 200), a repeated typo in the game script
1999 // means that `setScale` is called accidentally instead of `setScaler`.
2000 // In SSCI this did not do much because the first argument happened to be
2001 // smaller than the y-position of `ego`, but in ScummVM the first argument is
2002 // larger and so a debug message "y value less than vanishingY" is displayed.
2003 // This is the same issue as with LSL6 hires.
2004 static const uint16 hoyle5SetScaleSignature[] = {
2005 SIG_MAGICDWORD,
2006 0x38, SIG_SELECTOR16(setScale), // pushi setScale ($14b)
2007 0x38, SIG_UINT16(0x05), // pushi 5
2008 0x51, 0x2c, // class Scaler
2009 SIG_END
2010 };
2011
2012 static const uint16 hoyle5PatchSetScale[] = {
2013 0x38, PATCH_SELECTOR16(setScaler), // pushi setScaler ($14f)
2014 PATCH_END
2015 };
2016
2017 // There are two derived collections of Hoyle Classic Games:
2018 // 1) The Hoyle Children's Collection, which includes the following games:
2019 // - Crazy Eights (script 100)
2020 // - Old Maid (script 200)
2021 // - Checkers (script 1200)
2022 // 2) Hoyle Bridge, which includes the following games:
2023 // - Bridge (script 700)
2024 // In these two collections, the scripts for the other games have been removed.
2025 // Choosing any other game than the above results in a "No script found" error.
2026 // The original game did not show the game selection screen, as there were
2027 // direct shortucts to each game.
2028 // Since we do show the game selection screen, we remove all the games
2029 // which from the ones below, which are not included in each version:
2030 // - Crazy Eights (script 100)
2031 // - Old Maid (script 200)
2032 // - Hearts (script 300)
2033 // - Gin Rummy (script 400)
2034 // - Cribbage (script 500)
2035 // - Klondike / Solitaire (script 600)
2036 // - Bridge (script 700)
2037 // - Poker (script 1100)
2038 // - Checkers (script 1200)
2039 // - Backgammon (script 1300)
2040 static const uint16 hoyle5SignatureCrazyEights[] = {
2041 SIG_MAGICDWORD,
2042 0x38, 0x8e, 0x00, // pushi 008e
2043 0x76, // push0
2044 0x38, 0xf0, 0x02, // pushi 02f0
2045 0x76, // push0
2046 0x72, 0x9c, 0x01, // lofsa chooseCrazy8s
2047 0x4a, 0x08, 0x00, // send 0008
2048 SIG_END
2049 };
2050
2051 static const uint16 hoyle5SignatureOldMaid[] = {
2052 SIG_MAGICDWORD,
2053 0x38, 0x8e, 0x00, // pushi 008e
2054 0x76, // push0
2055 0x38, 0xf0, 0x02, // pushi 02f0
2056 0x76, // push0
2057 0x72, 0x2c, 0x02, // lofsa chooseOldMaid
2058 0x4a, 0x08, 0x00, // send 0008
2059 SIG_END
2060 };
2061
2062 static const uint16 hoyle5SignatureHearts[] = {
2063 SIG_MAGICDWORD,
2064 0x38, 0x8e, 0x00, // pushi 008e
2065 0x76, // push0
2066 0x38, 0xf0, 0x02, // pushi 02f0
2067 0x76, // push0
2068 0x72, 0xdc, 0x03, // lofsa chooseHearts
2069 0x4a, 0x08, 0x00, // send 0008
2070 SIG_END
2071 };
2072
2073 static const uint16 hoyle5SignatureGinRummy[] = {
2074 SIG_MAGICDWORD,
2075 0x38, 0x8e, 0x00, // pushi 008e
2076 0x76, // push0
2077 0x38, 0xf0, 0x02, // pushi 02f0
2078 0x76, // push0
2079 0x72, 0xbc, 0x02, // lofsa chooseGinRummy
2080 0x4a, 0x08, 0x00, // send 0008
2081 SIG_END
2082 };
2083
2084 static const uint16 hoyle5SignatureCribbage[] = {
2085 SIG_MAGICDWORD,
2086 0x38, 0x8e, 0x00, // pushi 008e
2087 0x76, // push0
2088 0x38, 0xf0, 0x02, // pushi 02f0
2089 0x76, // push0
2090 0x72, 0x4c, 0x03, // lofsa chooseCribbage
2091 0x4a, 0x08, 0x00, // send 0008
2092 SIG_END
2093 };
2094
2095 static const uint16 hoyle5SignatureKlondike[] = {
2096 SIG_MAGICDWORD,
2097 0x38, 0x8e, 0x00, // pushi 008e
2098 0x76, // push0
2099 0x38, 0xf0, 0x02, // pushi 02f0
2100 0x76, // push0
2101 0x72, 0xfc, 0x04, // lofsa chooseKlondike
2102 0x4a, 0x08, 0x00, // send 0008
2103 SIG_END
2104 };
2105
2106 static const uint16 hoyle5SignatureBridge[] = {
2107 SIG_MAGICDWORD,
2108 0x38, 0x8e, 0x00, // pushi 008e
2109 0x76, // push0
2110 0x38, 0xf0, 0x02, // pushi 02f0
2111 0x76, // push0
2112 0x72, 0x6c, 0x04, // lofsa chooseBridge
2113 0x4a, 0x08, 0x00, // send 0008
2114 SIG_END
2115 };
2116
2117 static const uint16 hoyle5SignaturePoker[] = {
2118 SIG_MAGICDWORD,
2119 0x38, 0x8e, 0x00, // pushi 008e
2120 0x76, // push0
2121 0x38, 0xf0, 0x02, // pushi 02f0
2122 0x76, // push0
2123 0x72, 0x8c, 0x05, // lofsa choosePoker
2124 0x4a, 0x08, 0x00, // send 0008
2125 SIG_END
2126 };
2127
2128 static const uint16 hoyle5SignatureCheckers[] = {
2129 SIG_MAGICDWORD,
2130 0x38, 0x8e, 0x00, // pushi 008e
2131 0x76, // push0
2132 0x38, 0xf0, 0x02, // pushi 02f0
2133 0x76, // push0
2134 0x72, 0x1c, 0x06, // lofsa chooseCheckers
2135 0x4a, 0x08, 0x00, // send 0008
2136 SIG_END
2137 };
2138
2139 static const uint16 hoyle5SignatureBackgammon[] = {
2140 SIG_MAGICDWORD,
2141 0x38, 0x8e, 0x00, // pushi 008e
2142 0x76, // push0
2143 0x38, 0xf0, 0x02, // pushi 02f0
2144 0x76, // push0
2145 0x72, 0xac, 0x06, // lofsa chooseBackgammon
2146 0x4a, 0x08, 0x00, // send 0008
2147 SIG_END
2148 };
2149
2150 static const uint16 hoyle5PatchDisableGame[] = {
2151 0x35, 0x00, // ldi 00
2152 0x35, 0x00, // ldi 00
2153 0x35, 0x00, // ldi 00
2154 0x35, 0x00, // ldi 00
2155 0x35, 0x00, // ldi 00
2156 0x35, 0x00, // ldi 00
2157 0x35, 0x00, // ldi 00
2158 PATCH_END
2159 };
2160
2161 // During Bridge, Declarer_Second_NT:think performs a bitwise or against an
2162 // object due to a script typo. This script bug is also in Hoyle4, see its
2163 // patch notes above for more detail.
2164 //
2165 // Applies to at least: English PC
2166 // Responsible method: Declarer_Second_NT:think
2167 // Fixes bug #11173
2168 static const uint16 hoyle5SignatureBridgeArithmetic[] = {
2169 0x36, // push [ bridgeHand:highCard ]
2170 0x34, SIG_UINT16(0x0f00), // ldi 0f00
2171 0x14, // or [ error: bridgeHand:highCard is an object ]
2172 0x36, // push
2173 0x63, 0x44, // pToa pard
2174 0x4a, SIG_UINT16(0x0008), // send 08 [ pard hasCard: theSuitLead (bridgeHand:highCard | 0f00) ]
2175 0x2f, 0x26, // bt 26
2176 0x7e, SIG_ADDTOOFFSET(+2), // line
2177 SIG_MAGICDWORD,
2178 0x83, 0x03, // lal 03
2179 0x2f, 0x1f, // bt 1f
2180 0x7e, SIG_ADDTOOFFSET(+2), // line
2181 0x38, // pushi rank
2182 SIG_END
2183 };
2184
2185 static const uint16 hoyle5PatchBridgeArithmetic[] = {
2186 0x38, PATCH_GETORIGINALUINT16(+24), // pushi rank
2187 0x76, // push0
2188 0x4a, PATCH_UINT16(0x0004), // send 04 [ bridgeHand:highCard rank? ]
2189 0x38, PATCH_UINT16(0x0f00), // pushi 0f00
2190 0x14, // or
2191 0x36, // push
2192 0x63, 0x44, // pToa pard
2193 0x4a, PATCH_UINT16(0x0008), // send 08 [ pard hasCard: theSuitLead ((bridgeHand:highCard):rank | 0f00) ]
2194 0x2f, 0x20, // bt 20
2195 0x83, 0x03, // lal 03
2196 0x2f, 0x1c, // bt 1c
2197 PATCH_END
2198 };
2199
2200 // script, description, signature patch
2201 static const SciScriptPatcherEntry hoyle5Signatures[] = {
2202 { true, 3, "remove kGetTime spin", 1, hoyle5SignatureSpinLoop, hoyle5PatchSpinLoop },
2203 { true, 23, "remove kGetTime spin", 1, hoyle5SignatureSpinLoop, hoyle5PatchSpinLoop },
2204 { true, 200, "fix setScale calls", 11, hoyle5SetScaleSignature, hoyle5PatchSetScale },
2205 { true, 500, "remove kGetTime spin", 1, hoyle5SignatureSpinLoop, hoyle5PatchSpinLoop },
2206 { true, 64937, "remove kGetTime spin", 1, hoyle5SignatureSpinLoop, hoyle5PatchSpinLoop },
2207 { true, 64908, "disable video benchmarking", 1, sci2BenchmarkSignature, sci2BenchmarkPatch },
2208 { true, 733, "bridge arithmetic against object ", 1, hoyle5SignatureBridgeArithmetic, hoyle5PatchBridgeArithmetic },
2209 // This entry has been placed so that the broken Poker game is disabled. This game uses an external DLL, PENGIN16.DLL,
2210 // which is invoked via kWinDLL. We need to reverse the logic in PENGIN16.DLL and call it directly, in order to get this
2211 // game to work properly. Until then, this game entry will be disabled.
2212 { true, 975, "disable Poker", 1, hoyle5SignaturePoker, hoyle5PatchDisableGame },
2213 { true, 64990, "increase number of save games (1/2)", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
2214 { true, 64990, "increase number of save games (2/2)", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
2215 { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
2216 SCI_SIGNATUREENTRY_TERMINATOR
2217 };
2218
2219 // script, description, signature patch
2220 static const SciScriptPatcherEntry hoyle5ChildrensCollectionSignatures[] = {
2221 { true, 3, "remove kGetTime spin", 1, hoyle5SignatureSpinLoop, hoyle5PatchSpinLoop },
2222 { true, 23, "remove kGetTime spin", 1, hoyle5SignatureSpinLoop, hoyle5PatchSpinLoop },
2223 { true, 200, "fix setScale calls", 11, hoyle5SetScaleSignature, hoyle5PatchSetScale },
2224 { true, 500, "remove kGetTime spin", 1, hoyle5SignatureSpinLoop, hoyle5PatchSpinLoop },
2225 { true, 64937, "remove kGetTime spin", 1, hoyle5SignatureSpinLoop, hoyle5PatchSpinLoop },
2226 { true, 64908, "disable video benchmarking", 1, sci2BenchmarkSignature, sci2BenchmarkPatch },
2227 { true, 975, "disable Gin Rummy", 1, hoyle5SignatureGinRummy, hoyle5PatchDisableGame },
2228 { true, 975, "disable Cribbage", 1, hoyle5SignatureCribbage, hoyle5PatchDisableGame },
2229 { true, 975, "disable Klondike", 1, hoyle5SignatureKlondike, hoyle5PatchDisableGame },
2230 { true, 975, "disable Bridge", 1, hoyle5SignatureBridge, hoyle5PatchDisableGame },
2231 { true, 975, "disable Poker", 1, hoyle5SignaturePoker, hoyle5PatchDisableGame },
2232 { true, 975, "disable Hearts", 1, hoyle5SignatureHearts, hoyle5PatchDisableGame },
2233 { true, 975, "disable Backgammon", 1, hoyle5SignatureBackgammon, hoyle5PatchDisableGame },
2234 SCI_SIGNATUREENTRY_TERMINATOR
2235 };
2236
2237 // script, description, signature patch
2238 static const SciScriptPatcherEntry hoyle5BridgeSignatures[] = {
2239 { true, 3, "remove kGetTime spin", 1, hoyle5SignatureSpinLoop, hoyle5PatchSpinLoop },
2240 { true, 23, "remove kGetTime spin", 1, hoyle5SignatureSpinLoop, hoyle5PatchSpinLoop },
2241 { true, 500, "remove kGetTime spin", 1, hoyle5SignatureSpinLoop, hoyle5PatchSpinLoop },
2242 { true, 64937, "remove kGetTime spin", 1, hoyle5SignatureSpinLoop, hoyle5PatchSpinLoop },
2243 { true, 64908, "disable video benchmarking", 1, sci2BenchmarkSignature, sci2BenchmarkPatch },
2244 { true, 733, "bridge arithmetic against object ", 1, hoyle5SignatureBridgeArithmetic, hoyle5PatchBridgeArithmetic },
2245 { true, 975, "disable Gin Rummy", 1, hoyle5SignatureGinRummy, hoyle5PatchDisableGame },
2246 { true, 975, "disable Cribbage", 1, hoyle5SignatureCribbage, hoyle5PatchDisableGame },
2247 { true, 975, "disable Klondike", 1, hoyle5SignatureKlondike, hoyle5PatchDisableGame },
2248 { true, 975, "disable Poker", 1, hoyle5SignaturePoker, hoyle5PatchDisableGame },
2249 { true, 975, "disable Hearts", 1, hoyle5SignatureHearts, hoyle5PatchDisableGame },
2250 { true, 975, "disable Backgammon", 1, hoyle5SignatureBackgammon, hoyle5PatchDisableGame },
2251 { true, 975, "disable Crazy Eights", 1, hoyle5SignatureCrazyEights, hoyle5PatchDisableGame },
2252 { true, 975, "disable Old Maid", 1, hoyle5SignatureOldMaid, hoyle5PatchDisableGame },
2253 { true, 975, "disable Checkers", 1, hoyle5SignatureCheckers, hoyle5PatchDisableGame },
2254 { true, 64990, "increase number of save games (1/2)", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
2255 { true, 64990, "increase number of save games (2/2)", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
2256 { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
2257 SCI_SIGNATUREENTRY_TERMINATOR
2258 };
2259
2260 #pragma mark -
2261 #pragma mark Gabriel Knight 1
2262
2263 // `daySixBeignet::changeState(4)` is called when the cop goes outside. It sets
2264 // cycles to 220. This is a CPU-speed dependent value and not usually enough
2265 // time to get to the door, so patch it to 22 seconds.
2266 //
2267 // Applies to at least: English PC-CD, German PC-CD, English Mac
2268 static const uint16 gk1Day6PoliceBeignetSignature1[] = {
2269 0x35, 0x04, // ldi 4
2270 0x1a, // eq?
2271 0x30, SIG_ADDTOOFFSET(+2), // bnt [next state check]
2272 0x38, SIG_SELECTOR16(dispose), // pushi dispose
2273 0x76, // push0
2274 0x72, SIG_ADDTOOFFSET(+2), // lofsa deskSarg
2275 0x4a, SIG_UINT16(0x04), // send 4
2276 SIG_MAGICDWORD,
2277 0x34, SIG_UINT16(0xdc), // ldi 220
2278 0x65, SIG_ADDTOOFFSET(+1), // aTop cycles ($1a for PC, $1c for Mac)
2279 0x32, // jmp [end]
2280 SIG_END
2281 };
2282
2283 static const uint16 gk1Day6PoliceBeignetPatch1[] = {
2284 PATCH_ADDTOOFFSET(+16),
2285 0x34, PATCH_UINT16(0x16), // ldi 22
2286 0x65, PATCH_GETORIGINALBYTEADJUST(+20, +2), // aTop seconds ($1c for PC, $1e for Mac)
2287 PATCH_END
2288 };
2289
2290 // On day 6 when the cop is outside for the beignet, walking through the
2291 // swinging door will also reset the puzzle timer so the player has 200 cycles
2292 // to get through the area before the cop returns. This is a CPU-speed
2293 // dependent value and not usually enough time to get to the door, so we patch
2294 // it to 20 seconds instead.
2295 //
2296 // Applies to at least: English PC-CD, German PC-CD, English Mac
2297 // Responsible method: sInGateWithPermission::changeState(0)
2298 // Fixes bug: #9805
2299 static const uint16 gk1Day6PoliceBeignetSignature2[] = {
2300 0x72, SIG_ADDTOOFFSET(+2), // lofsa daySixBeignet
2301 0x1a, // eq?
2302 0x31, 0x0d, // bnt [skip set cycles]
2303 0x38, SIG_SELECTOR16(cycles), // pushi cycles
2304 0x78, // push1
2305 SIG_MAGICDWORD,
2306 0x38, SIG_UINT16(0xc8), // pushi 200
2307 0x72, // lofsa
2308 SIG_END
2309 };
2310
2311 static const uint16 gk1Day6PoliceBeignetPatch2[] = {
2312 PATCH_ADDTOOFFSET(+6),
2313 0x38, PATCH_SELECTOR16(seconds), // pushi seconds
2314 0x78, // push1
2315 0x38, PATCH_UINT16(0x14), // pushi 20
2316 PATCH_END
2317 };
2318
2319 // `sargSleeping::changeState(8)` is called when the cop falls asleep and sets
2320 // the puzzle timer to 220 cycles. This is CPU-speed dependent and not usually
2321 // enough time to get to the door, so patch it to 22 seconds instead.
2322 //
2323 // Applies to at least: English PC-CD, German PC-CD, English Mac
2324 static const uint16 gk1Day6PoliceSleepSignature[] = {
2325 0x35, 0x08, // ldi 8
2326 0x1a, // eq?
2327 0x31, SIG_ADDTOOFFSET(+1), // bnt [next state check]
2328 SIG_MAGICDWORD,
2329 0x34, SIG_UINT16(0xdc), // ldi 220
2330 0x65, SIG_ADDTOOFFSET(+1), // aTop cycles ($1a for PC, $1c for Mac)
2331 0x32, // jmp [end]
2332 SIG_END
2333 };
2334
2335 static const uint16 gk1Day6PoliceSleepPatch[] = {
2336 PATCH_ADDTOOFFSET(+5),
2337 0x34, PATCH_UINT16(0x16), // ldi 22
2338 0x65, PATCH_GETORIGINALBYTEADJUST(+9, +2), // aTop seconds (1c for PC, 1e for Mac)
2339 PATCH_END
2340 };
2341
2342 // At the start of day 5, when the player already has the veve but still needs
2343 // to get the drum book, the drum book dialogue with Grace is played twice in
2344 // a row, and then the veve dialogue gets played again even though it was
2345 // already played during day 4.
2346 //
2347 // The duplicate drum book dialogue happens because it is triggered once in
2348 // `GetTheVeve::changeState(0)` and then again in `GetTheVeve::changeState(11)`.
2349 // The re-run of the veve dialogue happens because the game gives the player
2350 // the drum book in `GetTheVeVe::changeState(1)`, then *after* doing so, checks
2351 // if the player has the drum book and runs the veve dialogue if so.
2352 //
2353 // We fix both of these issues by skipping the has-drum-book check if the player
2354 // just got the drum book in 'GetTheVeve::changeState(1)'.
2355 // Doing this causes the game to jump from state 1 to state 12, which bypasses
2356 // the duplicate drum book dialogue in state 11, as well as the veve dialogue
2357 // trigger in the has-drum-book check.
2358 //
2359 // More notes: The veve newspaper item is inventory 9. The drum book is
2360 // inventory 14. The flag for veve research is 36, the flag for drum
2361 // research is 73.
2362 //
2363 // Special thanks, credits and kudos to sluicebox on IRC, who did a ton of
2364 // research on this and even found this game bug originally.
2365 //
2366 // Applies to at least: English PC-CD, German PC-CD
2367 static const uint16 gk1Day5DrumBookDialogueSignature[] = {
2368 0x31, 0x0b, // bnt [skip giving player drum book code]
2369 0x38, SIG_SELECTOR16(get), // pushi get ($200)
2370 0x78, // push1
2371 SIG_MAGICDWORD,
2372 0x39, 0x0e, // pushi $e
2373 0x81, 0x00, // lag global[0]
2374 0x4a, SIG_UINT16(0x06), // send 6 - GKEgo::get($e)
2375 // end of giving player drum book code
2376 0x38, SIG_SELECTOR16(has), // pushi has ($202)
2377 0x78, // push1
2378 0x39, 0x0e, // pushi $e
2379 0x81, 0x00, // lag global[0]
2380 0x4a, SIG_UINT16(0x06), // send 6 - GKEgo::has($e)
2381 0x18, // not
2382 0x30, SIG_UINT16(0x25), // bnt [veve newspaper code]
2383 SIG_END
2384 };
2385
2386 static const uint16 gk1Day5DrumBookDialoguePatch[] = {
2387 0x31, 0x0d, // bnt [skip giving player drum book code] adjusted
2388 PATCH_ADDTOOFFSET(+11), // skip give player drum book original code
2389 0x33, 0x0d, // jmp [over the check inventory for drum book code]
2390 // check inventory for drum book
2391 0x38, PATCH_SELECTOR16(has), // pushi has ($202)
2392 0x78, // push1
2393 0x39, 0x0e, // pushi $e
2394 0x81, 0x00, // lag global[0]
2395 0x4a, PATCH_UINT16(0x0006), // send 6 - GKEgo::has($e)
2396 0x2f, 0x23, // bt [veve newspaper code] (adjusted, saves 2 bytes)
2397 PATCH_END
2398 };
2399
2400 // When Gabriel goes to the phone, the script softlocks at
2401 // `startOfDay5::changeState(32)`.
2402 //
2403 // Applies to at least: English PC-CD, German PC-CD, English Mac
2404 static const uint16 gk1Day5PhoneFreezeSignature[] = {
2405 0x4a, // send ...
2406 SIG_MAGICDWORD, SIG_UINT16(0x0c), // ... $c
2407 0x35, 0x03, // ldi 3
2408 0x65, SIG_ADDTOOFFSET(+1), // aTop cycles
2409 0x32, SIG_ADDTOOFFSET(+2), // jmp [end]
2410 0x3c, // dup
2411 0x35, 0x21, // ldi $21
2412 SIG_END
2413 };
2414
2415 static const uint16 gk1Day5PhoneFreezePatch[] = {
2416 PATCH_ADDTOOFFSET(+3), // send $c
2417 0x35, 0x06, // ldi 6
2418 0x65, PATCH_GETORIGINALBYTEADJUST(+6, +6), // aTop ticks
2419 PATCH_END
2420 };
2421
2422 // When Gabriel is grabbing a vine, his saying "I can't believe I'm doing
2423 // this..." is cut off. We change it so the scripts wait for the audio.
2424 //
2425 // This is not supposed to be applied to the Floppy version.
2426 //
2427 // Applies to at least: English PC-CD, German PC-CD, Spanish PC-CD
2428 // Responsible method: vineSwing::changeState(1)
2429 // Fixes bug: #9820
2430 static const uint16 gk1Day9VineSwingSignature[] = {
2431 0x38, SIG_UINT16(0x0004), // pushi $4
2432 0x51, 0x17, // class CT
2433 0x36, // push
2434 0x39, 0x0b, // pushi $b
2435 0x78, // push1
2436 0x7c, // pushSelf
2437 0x81, 0x00, // lag global[$0]
2438 0x4a, SIG_UINT16(0x0020), // send $20
2439 0x38, SIG_SELECTOR16(setMotion), // pushi setMotion
2440 0x78, // push1
2441 0x76, // push0
2442 0x72, SIG_UINT16(0x0412), // lofsa guard1
2443 0x4a, SIG_UINT16(0x0006), // send $6
2444 0x38, SIG_SELECTOR16(say), // pushi say
2445 0x38, SIG_UINT16(0x0004), // pushi $4
2446 SIG_MAGICDWORD,
2447 0x39, 0x07, // pushi $7
2448 0x39, 0x08, // pushi $8
2449 0x39, 0x10, // pushi $10
2450 0x78, // push1
2451 0x81, 0x5b, // lag global[$5b]
2452 0x4a, SIG_UINT16(0x000c), // send $c
2453 SIG_END
2454 };
2455
2456 static const uint16 gk1Day9VineSwingPatch[] = {
2457 0x38, PATCH_UINT16(0x0003), // pushi $3
2458 0x51, 0x17, // class CT
2459 0x36, // push
2460 0x39, 0x0b, // pushi $b
2461 0x78, // push1
2462 0x81, 0x00, // lag global[$0]
2463 0x4a, PATCH_UINT16(0x001e), // send $20
2464 0x38, PATCH_SELECTOR16(setMotion), // pushi setMotion
2465 0x78, // push1
2466 0x76, // push0
2467 0x72, PATCH_UINT16(0x0412), // lofsa guard1
2468 0x4a, PATCH_UINT16(0x0006), // send $6
2469 0x38, PATCH_SELECTOR16(say), // pushi say
2470 0x38, PATCH_UINT16(0x0005), // pushi $5
2471 0x39, 0x07, // pushi $7
2472 0x39, 0x08, // pushi $8
2473 0x39, 0x10, // pushi $10
2474 0x78, // push1
2475 0x7c, // pushSelf
2476 0x81, 0x5b, // lag global[$5b]
2477 0x4a, PATCH_UINT16(0x000e), // send $c
2478 PATCH_END
2479 };
2480
2481 // The mummies on day 9 move without animating if ego exits to the north before
2482 // the first one finishes standing. This also occurs in Sierra's interpreter.
2483 //
2484 // The 12 outer rooms of the African mound all take place in room 710, which
2485 // reinitializes its contents on each room change. Each room's mummy is guard1
2486 // repositioned with a different view. When the mummies come to life,
2487 // keyWorks:changeState(6) starts guard1's standing animation after which
2488 // state 7 initializes guard1 for chasing ego. Ego however can leave before
2489 // guard1 finishes standing, preventing state 7 from occurring. The script for
2490 // exiting to the north assumes state 7 has run, otherwise guard1 remains on
2491 // the wrong view with no cycler or looper in subsequent rooms.
2492 //
2493 // This bug is due to the script rightWay only partially initializing guard1 for
2494 // chasing as opposed to wrongWay and backTrack which fully initialize. We fix
2495 // this by replacing rightWay's partial initialization with the full version
2496 // from keyWorks state 7. There are two versions of this patch due to
2497 // significant differences between floppy and CD versions of this script.
2498 //
2499 // This patch is not applied to the NRS versions of this script, which address
2500 // this bug by disabling control until guard1 finishes standing, giving the
2501 // player less time to escape.
2502 //
2503 // Applies to: All PC Floppy and CD versions. TODO: Test Mac
2504 // Responsible method: rightWay:changeState(1)
2505 // Fixes bug: #10828
2506 static const uint16 gk1MummyAnimateFloppySignature[] = {
2507 0x39, SIG_SELECTOR8(view), // pushi view [ full guard1 init ]
2508 SIG_MAGICDWORD,
2509 0x78, // push1
2510 0x38, SIG_UINT16(0x02c5), // pushi 709d
2511 SIG_ADDTOOFFSET(+674),
2512 0x38, SIG_SELECTOR16(setMotion), // pushi setMotion [ partial guard1 init ]
2513 0x38, SIG_UINT16(0x0004), // pushi 0004
2514 0x51, 0x70, // class PChase
2515 0x36, // push
2516 0x89, 0x00, // lsg global[0]
2517 0x39, 0x0f, // pushi 0f
2518 SIG_END
2519 };
2520
2521 static const uint16 gk1MummyAnimateFloppyPatch[] = {
2522 PATCH_ADDTOOFFSET(+680),
2523 0x39, PATCH_SELECTOR8(view), // pushi view [ waste 6 stack items to be compatible with ]
2524 0x76, // push0 [ the send instruction in full guard1 init ]
2525 0x39, PATCH_SELECTOR8(view), // pushi view
2526 0x76, // push0
2527 0x39, PATCH_SELECTOR8(view), // pushi view
2528 0x39, 0x00, // pushi 00
2529 0x32, PATCH_UINT16(0xfd4b), // jmp -693d [ continue full guard1 init in keyWorks state 7 ]
2530 PATCH_END
2531 };
2532
2533 static const uint16 gk1MummyAnimateCDSignature[] = {
2534 0x39, SIG_SELECTOR8(view), // pushi view [ full guard1 init ]
2535 SIG_MAGICDWORD,
2536 0x78, // push1
2537 0x38, SIG_UINT16(0x02c5), // pushi 709d
2538 SIG_ADDTOOFFSET(+750),
2539 0x38, SIG_SELECTOR16(setMotion), // pushi setMotion [ partial guard1 init ]
2540 0x38, SIG_UINT16(0x0004), // pushi 0004
2541 0x51, 0x70, // class PChase
2542 0x36, // push
2543 0x89, 0x00, // lsg global[0]
2544 0x39, 0x0f, // pushi 0f
2545 SIG_END
2546 };
2547
2548 static const uint16 gk1MummyAnimateCDPatch[] = {
2549 PATCH_ADDTOOFFSET(+756),
2550 0x39, PATCH_SELECTOR8(view), // pushi view [ waste 6 stack items to be compatible with ]
2551 0x76, // push0 [ the send instruction in full guard1 init ]
2552 0x39, PATCH_SELECTOR8(view), // pushi view
2553 0x76, // push0
2554 0x39, PATCH_SELECTOR8(view), // pushi view
2555 0x39, 0x00, // pushi 00
2556 0x32, PATCH_UINT16(0xfcff), // jmp -769d [ continue full guard1 init in keyWorks state 7 ]
2557 PATCH_END
2558 };
2559
2560 // In GK1, the `view` selector is used to store view numbers in some cases and
2561 // object references to Views in other cases. `Interrogation::dispose` compares
2562 // an object stored in the `view` selector with a number (which is not valid)
2563 // because its checks are in the wrong order. The check order was fixed in the
2564 // CD version, so just do what the CD version does.
2565 //
2566 // TODO: Check if English Mac is affected too and if this patch applies
2567 // Applies to at least: English Floppy
2568 static const uint16 gk1InterrogationBugSignature[] = {
2569 SIG_MAGICDWORD,
2570 0x65, 0x4c, // aTop $4c
2571 0x67, 0x50, // pTos $50
2572 0x34, SIG_UINT16(0x2710), // ldi $2710
2573 0x1e, // gt?
2574 0x31, 0x08, // bnt 8 [05a0]
2575 0x67, 0x50, // pTos $50
2576 0x34, SIG_UINT16(0x2710), // ldi $2710
2577 0x04, // sub
2578 0x65, 0x50, // aTop $50
2579 0x63, 0x50, // pToa $50
2580 0x31, 0x15, // bnt $15 [05b9]
2581 0x39, SIG_SELECTOR8(view), // pushi view ($e)
2582 0x76, // push0
2583 0x4a, SIG_UINT16(0x04), // send 4
2584 0xa5, 0x00, // sat temp[0]
2585 0x38, SIG_SELECTOR16(dispose), // pushi dispose
2586 0x76, // push0
2587 0x63, 0x50, // pToa $50
2588 0x4a, SIG_UINT16(0x04), // send 4
2589 0x85, 0x00, // lat temp[0]
2590 0x65, 0x50, // aTop $50
2591 SIG_END
2592 };
2593
2594 static const uint16 gk1InterrogationBugPatch[] = {
2595 0x65, 0x4c, // aTop $4c
2596 0x63, 0x50, // pToa $50
2597 0x31, 0x15, // bnt $15 [05b9]
2598 0x39, PATCH_SELECTOR8(view), // pushi view ($e)
2599 0x76, // push0
2600 0x4a, PATCH_UINT16(0x04), // send 4
2601 0xa5, 0x00, // sat temp[0]
2602 0x38, PATCH_SELECTOR16(dispose), // pushi dispose
2603 0x76, // push0
2604 0x63, 0x50, // pToa $50
2605 0x4a, PATCH_UINT16(0x04), // send 4
2606 0x85, 0x00, // lat temp[0]
2607 0x65, 0x50, // aTop $50
2608 0x67, 0x50, // pTos $50
2609 0x34, PATCH_UINT16(0x2710), // ldi $2710
2610 0x1e, // gt?
2611 0x31, 0x08, // bnt 8 [05b9]
2612 0x67, 0x50, // pTos $50
2613 0x34, PATCH_UINT16(0x2710), // ldi $2710
2614 0x04, // sub
2615 0x65, 0x50, // aTop $50
2616 PATCH_END
2617 };
2618
2619 // WORKAROUND: Script needed, because of differences in our pathfinding
2620 // algorithm.
2621 // In Madame Cazanoux's house, when Gabriel is leaving, he is placed on
2622 // the edge of the walkable area initially. This leads to a failure in
2623 // the pathfinding algorithm, and the pathfinding area is then ignored,
2624 // so Gabriel goes straight to the door by walking through the wall.
2625 // This is an edge case, which was apparently acceptable in SSCI. We
2626 // change the upper border of the walk area slightly, so that Gabriel
2627 // can be placed inside, and the pathfinding algorithm works correctly.
2628 //
2629 // Responsible method: rm280:init
2630 // Fixes bug: #9770
2631 static const uint16 gk1CazanouxPathfindingSignature[] = {
2632 SIG_MAGICDWORD,
2633 0x78, // push1 x = 1
2634 0x38, SIG_UINT16(0x0090), // pushi y = 144
2635 0x38, SIG_UINT16(0x00f6), // pushi x = 246
2636 0x38, SIG_UINT16(0x0092), // pushi y = 146
2637 0x38, SIG_UINT16(0x00f2), // pushi x = 242
2638 0x39, 0x69, // pushi y = 105
2639 0x39, 0x7c, // pushi x = 124
2640 0x39, 0x68, // pushi y = 104
2641 0x39, 0x56, // pushi x = 86
2642 0x39, 0x6f, // pushi y = 111
2643 0x39, 0x45, // pushi x = 69
2644 0x39, 0x7c, // pushi y = 124
2645 0x39, 0x2e, // pushi x = 46
2646 0x38, SIG_UINT16(0x0081), // pushi y = 129
2647 SIG_END
2648 };
2649
2650 static const uint16 gk1CazanouxPathfindingPatch[] = {
2651 PATCH_ADDTOOFFSET(+15),
2652 0x39, 0x7c, // pushi x = 124
2653 0x39, 0x67, // pushi y = 103 (was 104)
2654 PATCH_END
2655 };
2656
2657 // GK1 english pc floppy locks up on day 10 in the honfour (room 800) when
2658 // using the keycard on an unlocked door's keypad. This is due to mistakenly
2659 // calling handsOff instead of handsOn. Sierra fixed this in floppy patch 1.0a
2660 // and all other versions.
2661 //
2662 // We fix this by changing handsOff to handsOn and passing 0 as the caller
2663 // to gkMessager:say since the script disposes itself.
2664 //
2665 // Applies to: English PC Floppy only
2666 // Responsible method: sUnlockDoor:changeState(2)
2667 // Fixes bug: #10767
2668 static const uint16 gk1HonfourUnlockDoorSignature[] = {
2669 0x7c, // pushSelf
2670 0x81, 0x5b, // lag global[5b]
2671 0x4a, SIG_MAGICDWORD, // send e [ gkMessager:say ... self ]
2672 SIG_UINT16(0x000e),
2673 0x38, SIG_UINT16(0x0216), // pushi 0216 [ handsOff ]
2674 SIG_END
2675 };
2676
2677 static const uint16 gk1HonfourUnlockDoorPatch[] = {
2678 0x76, // push0
2679 0x81, 0x5b, // lag global[5b]
2680 0x4a, PATCH_UINT16(0x000e), // send e [ gkMessager:say ... 0 ]
2681 0x38, PATCH_UINT16(0x0217), // pushi 0217 [ handsOn ]
2682 PATCH_END
2683 };
2684
2685 // GK1 english pc floppy locks up on day 2 when using the binoculars to view
2686 // room 410 when the artist's drawing blows away. This is particularly bad
2687 // because when using the binoculars you can't use the mouse to access the
2688 // control panel to restore.
2689 //
2690 // We fix this as Sierra did in later versions by not allowing the drawing to
2691 // blow away when viewing through binoculars. To make room for this patch
2692 // we remove initializing juggler:cycleSpeed to 6 as this is redundant.
2693 // juggler is a Prop and Prop:cycleSpeed's initial value is 6.
2694 //
2695 // Applies to: English PC Floppy
2696 // Responsible method: neJackson:init
2697 // Fixes bug: #10797
2698 static const uint16 gk1Day2BinocularsLockupSignature[] = {
2699 SIG_MAGICDWORD,
2700 0x30, SIG_UINT16(0x01d6), // bnt 01d6 [ english pc floppy 1.0 only ]
2701 0x38, SIG_SELECTOR16(init), // pushi init
2702 0x76, // push0
2703 0x38, SIG_SELECTOR16(cycleSpeed), // pushi cycleSpeed
2704 0x78, // push1
2705 0x39, 0x06, // pushi 06
2706 0x38, SIG_SELECTOR16(setCycle), // pushi setCycle
2707 0x78, // push1
2708 0x51, 0x15, // class Fwd
2709 0x36, // push
2710 0x72, SIG_UINT16(0x02b0), // lofsa juggler
2711 0x4a, SIG_UINT16(0x0010), // send 10 [ juggler: init, cycleSpeed: 6, setCycle: Fwd ]
2712 0x38, SIG_SELECTOR16(init), // pushi init
2713 0x76, // push0
2714 0x72, SIG_UINT16(0x0538), // lofsa easel
2715 0x4a, SIG_UINT16(0x0004), // send 4 [ easel: init ]
2716 SIG_END
2717 };
2718
2719 static const uint16 gk1Day2BinocularsLockupPatch[] = {
2720 PATCH_ADDTOOFFSET(+6),
2721 0x3c, // dup
2722 0x76, // push0
2723 0x38, PATCH_SELECTOR16(setCycle), // pushi setCycle
2724 0x78, // push1
2725 0x51, 0x15, // class Fwd
2726 0x36, // push
2727 0x72, PATCH_UINT16(0x02b0), // lofsa juggler
2728 0x4a, PATCH_UINT16(0x000a), // send a [ juggler: init, setCycle Fwd ]
2729 0x76, // push0
2730 0x72, PATCH_UINT16(0x0538), // lofsa easel
2731 0x4a, PATCH_UINT16(0x0004), // send 4 [ easel: init ]
2732
2733 0x89, 0x0c, // lsg global[0c] [ previous room ]
2734 0x34, PATCH_UINT16(0x0190), // ldi 0190 [ overlook ]
2735 0x1c, // ne?
2736 0x31, 0x09, // bnt 09 [ drawing doesn't blow away ]
2737 PATCH_END
2738 };
2739
2740 // GK1 english pc floppy has a missing-points bug on day 5 in room 240.
2741 // Showing Mosely the veve sketch and Hartridge's notes awards 2 points
2742 // but not if you show the notes before the veve.
2743 // Sierra fixed this in floppy patch 1.0b and all other versions.
2744 //
2745 // We fix this by awarding 2 points when showing the veve second.
2746 //
2747 // Applies to: English PC Floppy
2748 // Responsible method: showMoselyPaper:changeState(5)
2749 // Fixes bug: #10763
2750 static const uint16 gk1Day5MoselyVevePointsSignature[] = {
2751 0x78, // push1
2752 0x39, 0x1b, // pushi 1b
2753 0x47, 0x0d, 0x00, SIG_UINT16(0x0002), // calle [export 0 of script 13], 02 [ is flag 1b set? ]
2754 0x30, SIG_UINT16(0x001e), // bnt 001e [ haven't shown notes yet ]
2755 0x78, // push1
2756 0x39, 0x1a, // pushi 1a
2757 0x47, 0x0d, 0x01, SIG_UINT16(0x0002), // calle [export 1 of script 13], 02 [ set flag 1a ]
2758 0x38, SIG_UINT16(0x00f2), // pushi 00f2 [ say ]
2759 0x38, SIG_UINT16(0x0005), // pushi 0005
2760 0x39, 0x11, // pushi 11 [ noun ]
2761 SIG_MAGICDWORD,
2762 0x39, 0x10, // pushi 10 [ verb ]
2763 0x39, 0x38, // pushi 38 [ cond ]
2764 0x76, // push0
2765 0x7c, // pushSelf
2766 0x81, 0x5b, // lag global[5b] [ GkMessager ]
2767 0x4a, SIG_UINT16(0x000e), // send 000e [ GkMessager:say ]
2768 0x32, SIG_UINT16(0x0013), // jmp 0013
2769 0x38, SIG_UINT16(0x00f2), // pushi 00f2 [ say ]
2770 SIG_END
2771 };
2772
2773 static const uint16 gk1Day5MoselyVevePointsPatch[] = {
2774 0x38, PATCH_UINT16(0x00f2), // pushi 00f2 [ say ]
2775 0x39, 0x05, // pushi 05
2776 0x39, 0x11, // pushi 11 [ noun ]
2777 0x39, 0x10, // pushi 10 [ verb ]
2778 0x78, // push1
2779 0x39, 0x1b, // pushi 1b
2780 0x47, 0x0d, 0x00, PATCH_UINT16(0x0002), // calle [export 0 of script 13], 02 [ is flag 1b set? ]
2781 0x31, 0x20, // bnt 20 [ pushi 37, continue GkMessager:say ]
2782 0x38, PATCH_UINT16(0x02fa), // pushi 02fa [ getPoints ]
2783 0x7a, // push2
2784 0x38, PATCH_UINT16(0xfc19), // pushi fc19 [ no flag ]
2785 0x7a, // push2 [ 2 points ]
2786 0x81, 0x00, // lag global[0]
2787 0x4a, PATCH_UINT16(0x0008), // send 8 [ GKEgo:getPoints -999 2 ]
2788 0x78, // push1
2789 0x39, 0x1a, // pushi 1a
2790 0x47, 0x0d, 0x01, PATCH_UINT16(0x0002), // calle [export 1 of script 13], 02 [ set flag 1a ]
2791 0x39, 0x38, // pushi 38 [ cond ]
2792 0x33, 0x09, // jmp 9 [ continue GkMessager:say ]
2793 PATCH_END
2794 };
2795
2796 // When turning on the museum's air conditioner prior to day 5 in room 260, the
2797 // timing is off and speech is interrupted. Some parts of the sequence run at
2798 // game speed and others don't, which at high speeds eliminates pauses between
2799 // dialogue. Dr. John's "We have air conditioning, you see" speech is cut off
2800 // at all speeds.
2801 //
2802 // We fix this by setting ego's speed to its default (6) during this sequence
2803 // and waiting for the messages to complete before proceeding. flipTheSwitch
2804 // restores ego's speed at the end of the script, even though it never sets it.
2805 //
2806 // Applies to: All CD versions
2807 // Responsible method: flipTheSwitch:changeState
2808 // Fixes bug: #11219
2809 static const uint16 gk1AirConditionerSpeechSignature[] = {
2810 0x30, SIG_UINT16(0x0020), // bnt 0020 [ state 1 ]
2811 SIG_ADDTOOFFSET(+26),
2812 0x4a, SIG_UINT16(0x000c), // send 0c
2813 0x32, SIG_UINT16(0x0409), // jmp 0409 [ end of method ]
2814 0x3c, // dup
2815 0x35, SIG_MAGICDWORD, 0x01, // ldi 01
2816 0x1a, // eq?
2817 0x30, SIG_UINT16(0x0056), // bnt 0056 [ state 2 ]
2818 SIG_ADDTOOFFSET(+24),
2819 0x4a, SIG_UINT16(0x001a), // send 1a [ GKEgo view: 265 ... ]
2820 SIG_ADDTOOFFSET(+33),
2821 0x4a, SIG_UINT16(0x000c), // send 0c
2822 0x32, SIG_UINT16(0x03c0), // jmp 03c0 [ end of method ]
2823 SIG_ADDTOOFFSET(+620),
2824 0x7a, // push2
2825 SIG_ADDTOOFFSET(+3),
2826 0x7c, // pushSelf
2827 0x81, 0x00, // lag 00
2828 0x4a, SIG_UINT16(0x0014), // send 14 [ GKEgo ... setCycle: End self ]
2829 SIG_ADDTOOFFSET(+8),
2830 0x38, SIG_UINT16(0x0004), // pushi 0004
2831 SIG_ADDTOOFFSET(+10),
2832 0x4a, SIG_UINT16(0x000c), // send 0c [ gkMessager say: 28 8 7 4 ]
2833 0x32, SIG_UINT16(0x012f), // jmp 012f [ end of method ]
2834 SIG_ADDTOOFFSET(+3),
2835 0x38, SIG_UINT16(0x0004), // pushi 0004
2836 SIG_ADDTOOFFSET(+9),
2837 0x4a, SIG_UINT16(0x000c), // send 0c [ gkMessager say: 28 8 8 4 ]
2838 0x32, SIG_UINT16(0x011a), // jmp 011a [ end of method ]
2839 SIG_ADDTOOFFSET(+6),
2840 0x38, SIG_SELECTOR16(stop), // pushi stop [ stop snake sound ]
2841 SIG_END
2842 };
2843
2844 static const uint16 gk1AirConditionerSpeechPatch[] = {
2845 0x30, PATCH_UINT16(0x001c), // bnt 001c [ state 1 ]
2846 PATCH_ADDTOOFFSET(+26),
2847 0x33, 0x47, // jmp 47 [ send 0c / end of method ]
2848 0x3c, // dup
2849 0x18, // not
2850 0x1a, // eq?
2851 0x31, 0x5c, // bnt 5c [ state 2 ]
2852 0x38, PATCH_SELECTOR16(cycleSpeed), // pushi cycleSpeed
2853 0x78, // push1
2854 0x39, 0x06, // pushi 06
2855 PATCH_ADDTOOFFSET(+24),
2856 0x4a, PATCH_UINT16(0x0020), // send 20 [ GKEgo cycleSpeed: 6 view: 265 ... ]
2857 PATCH_ADDTOOFFSET(+659),
2858 0x78, // push1
2859 PATCH_ADDTOOFFSET(+3),
2860 0x80, PATCH_UINT16(0x0000), // lag 0000
2861 0x4a, PATCH_UINT16(0x0012), // send 12 [ GKEgo ... setCycle: End ]
2862 PATCH_ADDTOOFFSET(+8),
2863 0x38, PATCH_UINT16(0x0005), // pushi 0005
2864 PATCH_ADDTOOFFSET(+10),
2865 0x7c, // pushSelf
2866 0x4a, PATCH_UINT16(0x000e), // send 0e [ gkMessager say: 28 8 7 4 self ]
2867 0x33, 0x1b, // jmp 1b [ stop snake sound ]
2868 PATCH_ADDTOOFFSET(+3),
2869 0x38, PATCH_UINT16(0x0005), // pushi 0005
2870 PATCH_ADDTOOFFSET(+9),
2871 0x7c, // pushSelf
2872 0x4a, PATCH_UINT16(0x000e), // send 0e [ gkMessager say: 28 8 8 4 self ]
2873 0x33, 0x06, // jmp 06 [ stop snake sound ]
2874 PATCH_END
2875 };
2876
2877 // The day 5 snake attack has speed, audio, and graphics problems.
2878 // These occur in all versions and also in Sierra's interpreter.
2879 //
2880 // Gabriel automatically walks cautiously in the darkened museum while looking
2881 // around and saying lines, then a snake drops on him. Depending on the game's
2882 // speed setting, the audio for "Why is it so dark in here?" is interrupted as
2883 // much as halfway through by the next line, "Dr. John, hello?". The cautious
2884 // walk animation runs at game speed, which can be fast, then abruptly changes
2885 // to 10 (33%) when the snake drops, which looks off. Ego doesn't even reach
2886 // the snake and instead stops short and warps 17 pixels to the right when the
2887 // drop animation starts.
2888 //
2889 // We fix all of this. Initializing ego's speed to 10 solves the interrupted
2890 // speech and inconsistent speed. It feels like this was the intended pacing.
2891 // The snake-warping isn't a speed issue, ego's animation frames for this
2892 // scene simply fall short of the snake's location. To fix that we start ego
2893 // a little farther in the room and increase ego's final position so that he
2894 // ends up directly under the snake and transitions to the drop animation
2895 // smoothly. Finally, we initialize ego on the room's first cycle instead of
2896 // second so that ego doesn't materialize after the room is already displayed.
2897 //
2898 // This patch works with pc floppy and cd even though they have different
2899 // snakeAttack scripts. Floppy doesn't have speech to interrupt but it
2900 // has the same issues.
2901 //
2902 // Applies to: All PC Floppy and CD versions. TODO: Test Mac, should apply
2903 // Responsible method: snakeAttack:changeState
2904 // Fixes bug: #10793
2905 static const uint16 gk1Day5SnakeAttackSignature1[] = {
2906 0x65, 0x1a, // aTop cycles
2907 0x32, SIG_ADDTOOFFSET(+2), // jmp [ end of method ]
2908 0x3c, // dup
2909 0x35, 0x01, // ldi 1
2910 SIG_MAGICDWORD,
2911 0x1a, // eq?
2912 0x30, SIG_UINT16(0x0048), // bnt 0048 [ state 2 ]
2913 0x35, 0x01, // ldi 1 [ free bytes ]
2914 0x39, SIG_SELECTOR8(view), // pushi view
2915 0x78, // push1
2916 0x38, SIG_UINT16(0x0107), // pushi 0107
2917 0x38, SIG_SELECTOR16(setCel), // pushi setCel
2918 0x78, // push1
2919 0x76, // push0
2920 0x38, SIG_SELECTOR16(setLoop), // pushi setLoop
2921 0x78, // push1
2922 0x76, // push0
2923 0x39, SIG_SELECTOR8(signal), // pushi signal
2924 0x78, // push1
2925 0x39, SIG_SELECTOR8(signal), // pushi signal
2926 0x76, // push0
2927 0x81, 0x00, // lag global[0]
2928 0x4a, SIG_UINT16(0x0004), // send 4 [ GKEgo:signal? ]
2929 SIG_ADDTOOFFSET(+18),
2930 0x39, 0x64, // pushi 64 [ initial x ]
2931 SIG_END
2932 };
2933
2934 static const uint16 gk1Day5SnakeAttackPatch1[] = {
2935 0x39, PATCH_SELECTOR8(view), // pushi view [ begin initializing ego in state 0 ]
2936 0x78, // push1
2937 0x33, 0x07, // jmp 07 [ continue initializing ego in state 0 ]
2938 0x3c, // dup
2939 0x18, // not [ acc = 1 ]
2940 0x1a, // eq?
2941 0x65, 0x1a, // aTop cycles [ just set cycles to 1 in state 1 ]
2942 0x33, 0x48, // jmp 47 [ state 2 ]
2943 0x38, PATCH_UINT16(0x0107), // pushi 0107
2944 0x39, PATCH_SELECTOR8(cel), // pushi cel
2945 0x78, // push1
2946 0x76, // push0
2947 0x38, PATCH_SELECTOR16(setLoop), // pushi setLoop
2948 0x78, // push1
2949 0x76, // push0
2950 0x39, PATCH_SELECTOR8(signal), // pushi signal
2951 0x78, // push1
2952 0x38, PATCH_SELECTOR16(cycleSpeed), // pushi cycleSpeed
2953 0x78, // push1
2954 0x39, 0x0a, // pushi 0a
2955 PATCH_ADDTOOFFSET(+5),
2956 0x4a, PATCH_UINT16(0x000a), // send a [ GKEgo:signal?, cycleSpeed = a ]
2957 PATCH_ADDTOOFFSET(+18),
2958 0x39, 0x70, // pushi 70 [ new initial x ]
2959 PATCH_END
2960 };
2961
2962 // This just changes ego's second x coordinate but unfortunately that promotes it to 16 bits
2963 static const uint16 gk1Day5SnakeAttackSignature2[] = {
2964 SIG_MAGICDWORD,
2965 0x39, 0x7a, // pushi 7a [ x for second walking loop ]
2966 0x39, 0x7c, // pushi 7c
2967 0x38, SIG_SELECTOR16(setCycle), // pushi setCycle
2968 0x7a, // push2
2969 0x51, 0x18, // class End
2970 0x36, // push
2971 0x7c, // pushSelf
2972 0x81, 0x00, // lag global[0]
2973 0x4a, SIG_UINT16(0x0022), // send 22
2974 0x32, SIG_ADDTOOFFSET(+2), // jmp [ end of method ]
2975 SIG_END
2976 };
2977
2978 static const uint16 gk1Day5SnakeAttackPatch2[] = {
2979 0x38, PATCH_UINT16(0x008b), // pushi 008b [ new x for second walking loop ]
2980 0x39, 0x7c, // pushi 7c
2981 0x38, PATCH_SELECTOR16(setCycle), // pushi setCycle
2982 0x7a, // push2
2983 0x51, 0x18, // class End
2984 0x36, // push
2985 0x7c, // pushSelf
2986 0x81, 0x00, // lag global[0]
2987 0x4a, PATCH_UINT16(0x0022), // send 22
2988 0x3a, // toss
2989 0x48, // ret
2990 PATCH_END
2991 };
2992
2993 // When entering the police station (room 230) sGabeEnters sets ego speed
2994 // to 4 for the door animation but fails to restore it to the game speed
2995 // by calling GKEgo:normalize. This leaves ego at 75% speed until doing
2996 // something that does call normalize.
2997 //
2998 // We fix this by calling GKEgo:normalize after Gabriel finishes walking
2999 // through the door in sGabeEnters:changeState(4). This replaces setting
3000 // GKEgo:ignoreActors to 0 but that's okay because normalize does that.
3001 //
3002 // Applies to: All PC Floppy and CD versions. TODO: Test Mac, should apply
3003 // Responsible method: sGabeEnters:changeState(4)
3004 // Fixes bug: #10780
3005 static const uint16 gk1PoliceEgoSpeedFixSignature[] = {
3006 0x38, SIG_MAGICDWORD, // pushi ignoreActors
3007 SIG_SELECTOR16(ignoreActors),
3008 0x78, // push1
3009 0x76, // push0
3010 0x81, 0x00, // lag global[0]
3011 0x4a, SIG_UINT16(0x000c), // send c [ GKEgo: ..., ignoreActors: 0 ]
3012 SIG_END
3013 };
3014
3015 static const uint16 gk1PoliceEgoSpeedFixPatch[] = {
3016 0x38, PATCH_SELECTOR16(normalize), // pushi normalize
3017 0x39, 0x00, // pushi 00
3018 0x81, 0x00, // lag global[0]
3019 0x4a, PATCH_UINT16(0x000a), // send a [ GKEgo: ..., normalize ]
3020 PATCH_END
3021 };
3022
3023 // When exiting the drugstore (room 250) egoExits sets ego speed to 15
3024 // (slowest) for the door animation but fails to restore it to game
3025 // speed by calling GKEgo:normalize. This leaves ego slow until doing
3026 // something that does call normalize.
3027 //
3028 // We fix this by calling GKEgo:normalize after the door animation.
3029 //
3030 // Applies to: All PC Floppy and CD versions. TODO: Test Mac, should apply
3031 // Responsible method: egoExits:changeState
3032 // Fixes bug: #10780
3033 static const uint16 gk1DrugStoreEgoSpeedFixSignature[] = {
3034 0x30, SIG_UINT16(0x003f), // bnt 003f [ state 1 ]
3035 SIG_ADDTOOFFSET(+60),
3036 SIG_MAGICDWORD,
3037 0x32, SIG_UINT16(0x0012), // jmp 12 [ end of method ]
3038 0x3c, // dup
3039 0x35, 0x01, // ldi 1
3040 0x1a, // eq?
3041 0x31, 0x0c, // bnt c [ end of method ]
3042 0x38, SIG_SELECTOR16(newRoom), // pushi newRoom
3043 0x78, // push1
3044 0x38, SIG_UINT16(0x00c8), // pushi 00c8 [ map ]
3045 0x81, 0x02, // lag global[2]
3046 0x4a, SIG_UINT16(0x0006), // send 6 [ rm250:newRoom = map ]
3047 0x3a, // toss
3048 SIG_END
3049 };
3050
3051 static const uint16 gk1DrugStoreEgoSpeedFixPatch[] = {
3052 0x3a, // toss
3053 0x31, 0x3d, // bnt 3d [ state 1 ]
3054 PATCH_ADDTOOFFSET(+60),
3055 0x48, // ret
3056 0x38, PATCH_SELECTOR16(normalize), // pushi normalize
3057 0x76, // push0
3058 0x81, 0x00, // lag global[0]
3059 0x4a, PATCH_UINT16(0x0004), // send 4 [ GKEgo:normalize ]
3060 0x38, PATCH_SELECTOR16(newRoom), // pushi newRoom
3061 0x78, // push1
3062 0x38, PATCH_UINT16(0x00c8), // pushi 00c8 [ map ]
3063 0x81, 0x02, // lag global[2]
3064 0x4a, PATCH_UINT16(0x0006), // send 6 [ rm250:newRoom = map ]
3065 PATCH_END
3066 };
3067
3068 // GK1 CD version cuts off Grace's speech when hanging up the phone on day 1.
3069 // This is a timing issue that also occurs in the original.
3070 //
3071 // startingCartoon:changeState(12) plays Grace's final phone message but doesn't
3072 // synchronize it with the script. Instead ego goes through a series of movements
3073 // that advance the state while Grace is speaking. Once the sequence is complete
3074 // Grace hangs up the phone and starts her next message which interrupts the
3075 // previous one. There is no mechanism to make sure that Grace's message has
3076 // first completed and so it cut offs the last one or two words. The timing only
3077 // worked in the original on slower machines that weren't able to run the
3078 // sequence at full speed.
3079 //
3080 // We fix this by adding a delay to startingCartoon:changeState(18) so that
3081 // Grace's speech has time to complete. This scene occurs before game speed
3082 // can be set and it plays at a consistent speed on ScummVM.
3083 //
3084 // This patch is only applied to CD versions. Floppies have a different script.
3085 //
3086 // Applies to: All CD versions
3087 // Responsible method: startingCartoon:changeState(18)
3088 // Fixes bug: #10787
3089 static const uint16 gk1Day1GracePhoneSignature[] = {
3090 SIG_MAGICDWORD,
3091 0x35, 0x12, // ldi 12
3092 0x1a, // eq?
3093 0x31, 0x2c, // bnt 2c
3094 SIG_ADDTOOFFSET(+28),
3095 0x38, SIG_UINT16(0x0003), // pushi 0003
3096 0x51, 0x69, // class Osc
3097 0x36, // push
3098 0x78, // push1
3099 0x7c, // pushSelf
3100 0x81, 0x00, // lag global[0]
3101 0x4a, SIG_UINT16(0x0024), // send 24 [ GKEgo: ... setCycle: Osc 1 self ]
3102 0x32, SIG_ADDTOOFFSET(+2), // jmp [ end of method ]
3103 SIG_END
3104 };
3105
3106 static const uint16 gk1Day1GracePhonePatch[] = {
3107 PATCH_ADDTOOFFSET(+33),
3108 0x7a, // push2
3109 0x51, 0x69, // class Osc
3110 0x36, // push
3111 0x78, // push1
3112 0x81, 0x00, // lag global[0]
3113 0x4a, PATCH_UINT16(0x0022), // send 22 [ GKEgo: ... setCycle: Osc 1 ]
3114
3115 // advance to the next state in 6 seconds instead of when Gabriel finishes
3116 // taking a sip of coffee, which takes 2 seconds, giving Grace's speech
3117 // an extra 4 seconds to complete.
3118 0x35, 0x06, // ldi 06
3119 0x65, 0x1c, // aTop seconds
3120
3121 0x3a, // toss
3122 0x48, // ret
3123 PATCH_END
3124 };
3125
3126 // French and Spanish CD versions contain an active debugging hotkey, ALT+N,
3127 // which brings up a series of unskippable bug-reporting dialogs and
3128 // eventually writes files to disk and crashes non-release builds due to
3129 // an uninitialized read. This hotkey is always active and not hidden
3130 // behind the game's debug mode flag so we just patch it out.
3131 //
3132 // Applies to: French and Spanish PC CD
3133 // Responsible method: GK:handleEvent
3134 // Fixes bug: #10781
3135 static const uint16 gk1SysLoggerHotKeySignature[] = {
3136 SIG_MAGICDWORD,
3137 0x34, SIG_UINT16(0x3100), // ldi 3100 [ ALT+N ]
3138 0x1a, // eq?
3139 0x31, // bnt
3140 SIG_END
3141 };
3142
3143 static const uint16 gk1SysLoggerHotKeyPatch[] = {
3144 PATCH_ADDTOOFFSET(+4),
3145 0x33, // jmp
3146 PATCH_END
3147 };
3148
3149 // After interrogating Gran in room 380, the room is re-initialized incorrectly.
3150 // Clicking on objects while seated causes Gabriel to briefly flicker into
3151 // standing and other frames. After standing, the knitting basket can be walked
3152 // through. These are script bugs which also occur in Sierra's interpreter.
3153 //
3154 // Ego is initialized incorrectly by rm380:init when returning from interrogation
3155 // (room 50). Several properties are wrong and it's bad luck that it works as
3156 // well as it does or Sierra would have noticed. For comparison, the scripts
3157 // egoEnters and sitDown do it correctly. rm380:init first initializes ego for
3158 // walking and then applies only some of the properties for sitting in the chair.
3159 //
3160 // This leaves ego in a walking/sitting state with several problems:
3161 // - signal flag kSignalDoesntTurn isn't set
3162 // - cycler is set to StopWalk instead of none
3163 // - loop/cel is set to 2 0 instead of 0 5
3164 //
3165 // rm380:init sets ego's loop/cel to 0 5 (Gabriel sitting) but the unexpected
3166 // StopWalk immediately changes this to 2 0 (Gabriel starts talking) which went
3167 // unnoticed because those two frames are similar. This is why Gabriel's hand
3168 // is slightly raised when returning from interrogation. The flickering is due
3169 // to ego attempting to turn to face items while sitting due to kSignalDoesntTurn
3170 // not being set.
3171 //
3172 // We fix the flickering by passing a second parameter to GKEgo:setLoop which
3173 // causes kSignalDoesntTurn to be set, preventing ego from attempting to face
3174 // objects being clicked, just as egoEnters and sitDown do. We fix the knitting
3175 // basket by adding its obstacle polygon to the room even when returning from
3176 // interrogation, which Sierra forgot to do.
3177 //
3178 // Applies to: All PC Floppy and CD versions. TODO: Test Mac, should apply
3179 // Responsible method: rm380:init
3180 // Fixes bug: #9760, #10707
3181 static const uint16 gk1GranRoomInitSignature[] = {
3182 0x38, SIG_SELECTOR16(setCel), // pushi setCel
3183 0x78, // push1
3184 0x39, 0x05, // pushi 05
3185 0x38, SIG_SELECTOR16(setLoop), // pushi setLoop
3186 0x78, // push1
3187 0x76, // push0 [ loop: 0 ]
3188 0x38, SIG_SELECTOR16(init), // pushi init
3189 0x76, // push0
3190 0x38, SIG_SELECTOR16(posn), // pushi posn
3191 SIG_MAGICDWORD,
3192 0x7a, // push2
3193 0x38, SIG_UINT16(0x00af), // pushi 00af
3194 0x39, 0x75, // pushi 75
3195 0x81, 0x00, // lag global[0]
3196 0x4a, SIG_UINT16(0x001e), // send 1e [ GKEgo: ... setCel: 5, setLoop: 0 ... ]
3197 0x35, 0x01, // ldi 1
3198 0xa3, 0x00, // sal local[0] [ 1, a non-zero value indicates ego is sitting ]
3199 SIG_END
3200 };
3201
3202 static const uint16 gk1GranRoomInitPatch[] = {
3203 0x39, PATCH_SELECTOR8(cel), // pushi cel [ use cel instead of equivalent setCel to save a byte ]
3204 0x78, // push1
3205 0x39, 0x05, // pushi 05
3206 0x38, PATCH_SELECTOR16(setLoop), // pushi setLoop
3207 0x7a, // push2
3208 0x76, // push0 [ loop: 0 ]
3209 0x78, // push1 [ 2nd param tells setLoop to set kSignalDoesntTurn ]
3210 0x38, PATCH_SELECTOR16(init), // pushi init
3211 0x76, // push0
3212 0x38, PATCH_SELECTOR16(posn), // pushi posn
3213 0x7a, // push2
3214 0x38, PATCH_UINT16(0x00af), // pushi 00af
3215 0x39, 0x75, // pushi 75
3216 0x81, 0x00, // lag global[0]
3217 0xa3, 0x00, // sal local[0] [ setting a non-zero object instead of 1 saves 2 bytes ]
3218 0x4a, PATCH_UINT16(0x0020), // send 20 [ GKEgo: ... cel: 5, setLoop: 0 1 ... ]
3219 0x33, 0x87, // jmp -79 [ add knitting basket obstacle to room ]
3220 PATCH_END
3221 };
3222
3223 // After phoning Wolfgang on day 7, Gabriel is placed beyond room 220's obstacle
3224 // boundary and can walk through walls and behind the room. This also occurs in
3225 // the original. The script inconsistently uses accessible and inaccessible
3226 // positions for placing ego next to the phone. We patch all instances to use
3227 // the accessible position.
3228 //
3229 // Applies to: All PC Floppy and CD versions. TODO: Test Mac, should apply
3230 // Responsible methods: rm220:init, useThePhone:changeState(0)
3231 // Fixes bug: #10853
3232 static const uint16 gk1EgoPhonePositionSignature[] = {
3233 SIG_MAGICDWORD,
3234 0x39, 0x68, // pushi 68 [ x: 104 ]
3235 0x39, 0x7e, // pushi 7e [ y: 126 ]
3236 SIG_END
3237 };
3238
3239 static const uint16 gk1EgoPhonePositionPatch[] = {
3240 0x39, 0x6b, // pushi 6b [ x: 107 ]
3241 0x39, 0x7c, // pushi 7c [ y: 124 ]
3242 PATCH_END
3243 };
3244
3245 // Restarting the game doesn't reset the current inventory item in the icon bar.
3246 // The previously selected item can then be used on day 1.
3247 //
3248 // Room 93 restarts the game and resets inventory by setting each item's owner
3249 // to zero. It makes no attempt to reset the icon bar. We fix this by instead
3250 // calling GKEgo:put on each item and passing zero for the new owner, as this
3251 // handles updating the icon bar when dropping an item. The "state" property is
3252 // no longer cleared for items but that's okay because it's never set or used.
3253 //
3254 // Applies to: All versions
3255 // Responsible method: doTheRestart:changeState(0)
3256 // Fixes bug: #11222
3257 static const uint16 gk1RestartInventorySignature[] = {
3258 SIG_MAGICDWORD,
3259 0x38, SIG_SELECTOR16(owner), // pushi owner
3260 0x78, // push1
3261 0x76, // push0
3262 0x39, SIG_SELECTOR8(state), // pushi state
3263 0x78, // push1
3264 0x76, // push0
3265 0x39, SIG_SELECTOR8(at), // pushi at
3266 0x78, // push1
3267 0x8b, 0x00, // lsl 00
3268 0x81, 0x09, // lag 09
3269 0x4a, SIG_UINT16(0x0006), // send 06 [ GKInventory at: local0 ]
3270 0x4a, SIG_UINT16(0x000c), // send 0c [ item owner: 0 state: 0 ]
3271 SIG_END
3272 };
3273
3274 static const uint16 gk1RestartInventoryPatch[] = {
3275 0x38, PATCH_SELECTOR16(put), // pushi put
3276 0x7a, // push2
3277 0x8b, 0x00, // lsl 00
3278 0x76, // push0
3279 0x81, 0x00, // lag 00
3280 0x4a, PATCH_UINT16(0x0008), // send 08 [ GKEgo put: local0 0 ]
3281 0x33, 0x08, // jmp 08
3282 PATCH_END
3283 };
3284
3285 // script, description, signature patch
3286 static const SciScriptPatcherEntry gk1Signatures[] = {
3287 { true, 0, "remove alt+n syslogger hotkey", 1, gk1SysLoggerHotKeySignature, gk1SysLoggerHotKeyPatch },
3288 { true, 51, "fix interrogation bug", 1, gk1InterrogationBugSignature, gk1InterrogationBugPatch },
3289 { true, 93, "fix inventory on restart", 1, gk1RestartInventorySignature, gk1RestartInventoryPatch },
3290 { true, 211, "fix day 1 grace phone speech timing", 1, gk1Day1GracePhoneSignature, gk1Day1GracePhonePatch },
3291 { true, 212, "fix day 5 drum book dialogue error", 1, gk1Day5DrumBookDialogueSignature, gk1Day5DrumBookDialoguePatch },
3292 { true, 212, "fix day 5 phone softlock", 1, gk1Day5PhoneFreezeSignature, gk1Day5PhoneFreezePatch },
3293 { true, 220, "fix ego phone position", 2, gk1EgoPhonePositionSignature, gk1EgoPhonePositionPatch },
3294 { true, 230, "fix day 6 police beignet timer issue (1/2)", 1, gk1Day6PoliceBeignetSignature1, gk1Day6PoliceBeignetPatch1 },
3295 { true, 230, "fix day 6 police beignet timer issue (2/2)", 1, gk1Day6PoliceBeignetSignature2, gk1Day6PoliceBeignetPatch2 },
3296 { true, 230, "fix day 6 police sleep timer issue", 1, gk1Day6PoliceSleepSignature, gk1Day6PoliceSleepPatch },
3297 { true, 230, "fix police station ego speed", 1, gk1PoliceEgoSpeedFixSignature, gk1PoliceEgoSpeedFixPatch },
3298 { true, 240, "fix day 5 mosely veve missing points", 1, gk1Day5MoselyVevePointsSignature, gk1Day5MoselyVevePointsPatch },
3299 { true, 250, "fix ego speed when exiting drug store", 1, gk1DrugStoreEgoSpeedFixSignature, gk1DrugStoreEgoSpeedFixPatch },
3300 { true, 260, "fix air conditioner speech timing", 1, gk1AirConditionerSpeechSignature, gk1AirConditionerSpeechPatch },
3301 { true, 260, "fix day 5 snake attack (1/2)", 1, gk1Day5SnakeAttackSignature1, gk1Day5SnakeAttackPatch1 },
3302 { true, 260, "fix day 5 snake attack (2/2)", 1, gk1Day5SnakeAttackSignature2, gk1Day5SnakeAttackPatch2 },
3303 { true, 280, "fix pathfinding in Madame Cazanoux's house", 1, gk1CazanouxPathfindingSignature, gk1CazanouxPathfindingPatch },
3304 { true, 380, "fix Gran's room obstacles and ego flicker", 1, gk1GranRoomInitSignature, gk1GranRoomInitPatch },
3305 { true, 410, "fix day 2 binoculars lockup", 1, gk1Day2BinocularsLockupSignature, gk1Day2BinocularsLockupPatch },
3306 { true, 710, "fix day 9 vine swing speech playing", 1, gk1Day9VineSwingSignature, gk1Day9VineSwingPatch },
3307 { true, 710, "fix day 9 mummy animation (floppy)", 1, gk1MummyAnimateFloppySignature, gk1MummyAnimateFloppyPatch },
3308 { true, 710, "fix day 9 mummy animation (cd)", 1, gk1MummyAnimateCDSignature, gk1MummyAnimateCDPatch },
3309 { true, 800, "fix day 10 honfour unlock door lockup", 1, gk1HonfourUnlockDoorSignature, gk1HonfourUnlockDoorPatch },
3310 { true, 64908, "disable video benchmarking", 1, sci2BenchmarkSignature, sci2BenchmarkPatch },
3311 { true, 64990, "increase number of save games (1/2)", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
3312 { true, 64990, "increase number of save games (2/2)", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
3313 { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
3314 SCI_SIGNATUREENTRY_TERMINATOR
3315 };
3316
3317 #pragma mark -
3318 #pragma mark Gabriel Knight 2
3319
3320 // GK2's inventory scrolls smoothly when the mouse is held down in the original
3321 // due to an inner loop in ScrollButton:track, but this causes slow scrolling
3322 // in our interpreter since we throttle kFrameOut. The script's inner loop is
3323 // itself throttled by ScrollButton:moveDelay, which is set to 25 and limits
3324 // event processing to every 25th iteration. Removing this delay results in
3325 // smooth scrolling as in the original.
3326 //
3327 // Applies to: All versions
3328 // Responsible method: ScrollButton:track
3329 static const uint16 gk2InventoryScrollSpeedSignature[] = {
3330 SIG_MAGICDWORD,
3331 0x63, 0x9c, // pToa moveDelay [ 25 ]
3332 0xa5, 0x02, // sat 02
3333 SIG_END
3334 };
3335
3336 static const uint16 gk2InventoryScrollSpeedPatch[] = {
3337 0x35, 0x01, // ldi 01
3338 PATCH_END
3339 };
3340
3341 // The down scroll button in GK2 jumps up a pixel on mousedown because there is
3342 // a send to scrollSelections using an immediate value 1, which means to scroll
3343 // up by 1 pixel. This patch fixes the send to scrollSelections by passing the
3344 // button's delta instead of 1. The Italian version's vocab.997 is missing the
3345 // scrollSelections selector so this patch avoids referencing it. Two versions
3346 // are necessary to accomodate scripts compiled with and without line numbers.
3347 //
3348 // Applies to: All versions
3349 // Responsible method: ScrollButon:track
3350 // Fixes bug: #9648
3351 static const uint16 gk2InventoryScrollDirSignature1[] = {
3352 SIG_MAGICDWORD,
3353 0x78, // push1
3354 0x63, 0x98, // pToa client
3355 0x4a, SIG_UINT16(0x0006), // send 06 [ client scrollSelections: 1 ]
3356 0x7e, // line
3357 SIG_END
3358 };
3359
3360 static const uint16 gk2InventoryScrollDirPatch1[] = {
3361 0x66, PATCH_UINT16(0x009a), // pTos delta
3362 0x62, PATCH_UINT16(0x0098), // pToa client
3363 0x4a, PATCH_UINT16(0x0006), // send 06 [ client scrollSelections: delta ]
3364 PATCH_END
3365 };
3366
3367 static const uint16 gk2InventoryScrollDirSignature2[] = {
3368 0x78, // push1
3369 0x63, 0x98, // pToa client
3370 0x4a, SIG_MAGICDWORD, // send 06 [ client scrollSelections: 1 ]
3371 SIG_UINT16(0x0006),
3372 0x35, 0x02, // ldi 02
3373 0x65, 0x56, // aTop cel
3374 SIG_END
3375 };
3376
3377 static const uint16 gk2InventoryScrollDirPatch2[] = {
3378 0x67, 0x9a, // pTos delta
3379 0x63, 0x98, // pToa client
3380 0x4a, PATCH_UINT16(0x0006), // send 06 [ client scrollSelections: delta ]
3381 0x7a, // push2
3382 0x69, 0x56, // sTop cel
3383 PATCH_END
3384 };
3385
3386 // The init code 'GK2::init' that runs when GK2 starts up unconditionally resets
3387 // the music volume to 63, but the game should always use the volume stored in
3388 // ScummVM.
3389 // Applies to: All versions
3390 // Fixes bug: #9700
3391 static const uint16 gk2VolumeResetSignature[] = {
3392 SIG_MAGICDWORD,
3393 0x35, 0x3f, // ldi $3f
3394 0xa1, 0x4c, // sag global[$4c] (music volume)
3395 SIG_END
3396 };
3397
3398 static const uint16 gk2VolumeResetPatch[] = {
3399 0x33, 0x02, // jmp 2 [past volume changes]
3400 PATCH_END
3401 };
3402
3403 // GK2 has custom video benchmarking code that needs to be disabled in a local
3404 // procedure called from GK2:init. It sets the game's detailLevel and returns
3405 // a value which is assigned to GK2:speedRating and never used. The maximum
3406 // detailLevel the game recognizes is six so we just set it to that.
3407 //
3408 // Applies to: All versions
3409 // Responsible method: GK2:init
3410 static const uint16 gk2BenchmarkSignature[] = {
3411 0x76, // push0
3412 0x40, SIG_ADDTOOFFSET(+2), // call speed test proc
3413 SIG_MAGICDWORD,
3414 SIG_UINT16(0x0000),
3415 0x65, 0x28, // aTop speedRating
3416 SIG_END
3417 };
3418
3419 static const uint16 gk2BenchmarkPatch[] = {
3420 0x35, 0x06, // ldi 06
3421 0x65, 0x18, // aTop _detailLevel
3422 0x33, 0x02, // jmp 02
3423 PATCH_END
3424 };
3425
3426 // GK2 has a complex sound bug which causes seemingly random lockups when
3427 // changing rooms in many areas including the Herrenchiemse Museum, the Hunt
3428 // Club, and St. Georg Church. This also occurs in the original.
3429 //
3430 // SoundManager continuously plays an array of sounds provided to its play
3431 // method. Sounds play in a random order with a random delay of five to ten
3432 // seconds in between. SoundManager is attached to soundRegion and survives
3433 // room changes. Rooms that set a new playlist call play on initialization.
3434 // The problem is that SoundManager:play doesn't clear its delay timer. If play
3435 // is called during a delay then the timer continues and expires during the
3436 // next sound. This is noticeable throughout the game when background music is
3437 // randomly interrupted by different music. Many room scripts change rooms by
3438 // calling SoundManager:fade in handsOff mode and proceeding once they've been
3439 // cued. If a stray SoundManager timer expires while a script is waiting for
3440 // fade to complete then SoundManager:cue will play the next sound, overwrite
3441 // gk2Music:client with itself, and the waiting script will never cue.
3442 //
3443 // We fix this by clearing SoundManager's timer state in SoundManager:play.
3444 // This prevents the delay timer from ever running while music is playing.
3445 //
3446 // Applies to: All versions
3447 // Responsible method: SoundManager:play
3448 static const uint16 gk2SoundManagerLockupSignature1[] = {
3449 0x7e, SIG_ADDTOOFFSET(+2), // line
3450 0x7e, SIG_ADDTOOFFSET(+2), // line
3451 SIG_MAGICDWORD,
3452 0x35, 0x00, // ldi 00
3453 0x65, 0x34, // aTop cleanup
3454 SIG_END
3455 };
3456
3457 static const uint16 gk2SoundManagerLockupPatch1[] = {
3458 0x35, 0x00, // ldi 00
3459 0x64, PATCH_UINT16(0x001e), // aTop seconds
3460 0x64, PATCH_UINT16(0x0012), // aTop scratch
3461 PATCH_END
3462 };
3463
3464 static const uint16 gk2SoundManagerLockupSignature2[] = {
3465 0x87, SIG_MAGICDWORD, 0x00, // lap 00
3466 0x18, // not
3467 0x31, 0x10, // bnt 10 [ skip debug message ]
3468 0x78, // push1
3469 0x72, // lofsa "WARNING: 0 args passed to SoundManager!"
3470 SIG_END
3471 };
3472
3473 static const uint16 gk2SoundManagerLockupPatch2[] = {
3474 0x35, 0x00, // ldi 00
3475 0x65, 0x1e, // aTop seconds
3476 0x65, 0x12, // aTop scratch
3477 0x32, PATCH_UINT16(0x0014), // jmp 0014 [ skip debug message ]
3478 PATCH_END
3479 };
3480
3481 // Clicking on Frau Miller in room 810 after exhausting her topics and then
3482 // clicking on anything else can lockup or crash the game. rm810:newRoom fades
3483 // the music before transitioning to room 8110, which takes several seconds.
3484 // The game doesn't disable input during this period and if the player begins
3485 // another action then rm810:cue can unexpectedly interrupt it. If Grace is
3486 // walking then the room will reload in a handsOff state. Other edge cases
3487 // include setting the room number to zero and subsequently crashing.
3488 //
3489 // We fix this by calling handsOff so that the player can't interrupt the Frau
3490 // Miller room transition while waiting for the music to fade, which is
3491 // consistent with the exit to the map.
3492 //
3493 // Applies to: All versions
3494 // Responsible method: rm810:newRoom
3495 static const uint16 gk2FrauMillerLockupSignature[] = {
3496 SIG_MAGICDWORD,
3497 0x39, 0x03, // pushi 03
3498 0x8f, 0x01, // lsp 01
3499 0x38, SIG_UINT16(0x1fae), // pushi 1fae
3500 0x38, SIG_UINT16(0x0320), // pushi 0320
3501 0x46, SIG_UINT16(0xfde7), // calle proc64999_5 [ OneOf newRoomNumber 8110 800 ]
3502 SIG_UINT16(0x0005),
3503 SIG_UINT16(0x0006),
3504 0x31, // bnt [ don't fade music ]
3505 SIG_END
3506 };
3507
3508 static const uint16 gk2FrauMillerLockupPatch[] = {
3509 0x8f, 0x01, // lsp 01
3510 0x34, PATCH_UINT16(0x1fae), // ldi 1fae
3511 0x24, // le? [ newRoomNumber <= 8110 ]
3512 0x31, PATCH_GETORIGINALBYTEADJUST(+18, +11), // bnt [ don't fade music ]
3513 0x38, PATCH_SELECTOR16(handsOff), // pushi handsOff
3514 0x39, 0x00, // pushi 00
3515 0x80, PATCH_UINT16(0x0001), // lag 0001
3516 0x4a, PATCH_UINT16(0x0004), // send 04 [ GK2 handsOff: ]
3517 PATCH_END
3518 };
3519
3520 // GK2 1.0 contains a deadend bug in chapter 3. Exhausting Leber's topics before
3521 // reading Grace's letter prevents returning to the police station to ask about
3522 // the Black Wolf, which is necessary to complete the chapter.
3523 //
3524 // We fix this as Sierra did by adding a flag 218 test so that the police
3525 // station doesn't close before Leber has been asked about the Black Wolf.
3526 //
3527 // Applies to: English PC 1.0
3528 // Responsible method: rm3210:dispose
3529 static const uint16 gk2PoliceStationDeadendSignature[] = {
3530 0x78, // push1
3531 0x38, SIG_UINT16(0x00dd), // pushi 00dd
3532 0x47, 0x0b, 0x00, SIG_MAGICDWORD, // calle proc11_0 [ is flag 221 set? ]
3533 SIG_UINT16(0x002),
3534 0x31, 0x51, // bnt 51 [ skip closing police station ]
3535 SIG_END
3536 };
3537
3538 static const uint16 gk2PoliceStationDeadendPatch[] = {
3539 0x80, PATCH_UINT16(0x00a3), // lag 00a3 [ flags 208-223 ]
3540 0x39, 0x24, // pushi 24
3541 0x12, // and
3542 0x39, 0x24, // pushi 24
3543 0x1a, // eq? [ are flags 218 and 221 set? ]
3544 PATCH_END
3545 };
3546
3547 // In chapter 3, Xaver can be asked about the Black Wolf before learning about
3548 // the Black Wolf from Grace's letter. The tBlackWolf topic in room 4320 is
3549 // missing the readyFlagNum value of 514 that the other tBlackWolf topics in
3550 // chapter 3 have, so we set it.
3551 //
3552 // Applies to: All versions
3553 // Responsible method: heap in script 4320
3554 static const uint16 gk2XaverBlackWolfSignature[] = {
3555 SIG_MAGICDWORD, // tBlackWolf
3556 SIG_UINT16(0x010e), // sceneNum = 270
3557 SIG_UINT16(0x00f0), // flagNum = 240
3558 SIG_UINT16(0x0000), // readyFlagNum = 0
3559 SIG_END
3560 };
3561
3562 static const uint16 gk2XaverBlackWolfkPatch[] = {
3563 PATCH_ADDTOOFFSET(+4),
3564 PATCH_UINT16(0x0202), // readyFlagNum = 514
3565 PATCH_END
3566 };
3567
3568 // Chapter 4 has a bug in many versions of GK2 that is effectively a deadend.
3569 // Asking Georg about "Ludwig's letter to the Conductor" is required to finish
3570 // the chapter. This topic becomes available after looking at the "Ludwig and
3571 // Wagner" plaque in Herrenchiemsee and then clicking again to read it aloud.
3572 // The problem is that the rest of the game only cares about looking at the
3573 // plaque. If Herrenchiemsee is completed without reading the plaque then the
3574 // Hint feature claims everything is done and the player appears to be stuck.
3575 //
3576 // We fix this as Sierra did by making Georg's letter topic available upon just
3577 // looking at the plaque, which is consistent with the rest of the scripts.
3578 // Although Sierra advertised this fix in the readme for GK2PAT 1.11, the patch
3579 // file seems to be missing, but appears in later versions and the GOG release.
3580 //
3581 // Applies to: English PC 1.0, 1.1, 1.11 Patch, Mac
3582 // Responsible method: Heap in script 8520
3583 static const uint16 gk2GeorgLetterTopicSignature[] = {
3584 SIG_MAGICDWORD, // tLtr2Conductor
3585 SIG_UINT16(0x0211), // sceneNum = 529
3586 SIG_UINT16(0x012a), // flagNum = 298
3587 SIG_UINT16(0x0283), // readyFlagNum = 643
3588 SIG_END
3589 };
3590
3591 static const uint16 gk2GeorgLetterTopicPatch[] = {
3592 PATCH_ADDTOOFFSET(+4),
3593 PATCH_UINT16(0x026f), // readyFlagNum = 623
3594 PATCH_END
3595 };
3596
3597 // In early versions of GK2, clicking on the holy water basket after using the
3598 // holy water locks up the game. The script is supposed to test the flag that's
3599 // set when getting the water but some code mistakenly tests inventory instead.
3600 // We fix this as Sierra did by replacing the inventory tests with flag tests.
3601 //
3602 // Applies to: English PC 1.0, Mac
3603 // Responsible methods: waterBasket:handleEvent, waterBasket:doVerb
3604 static const uint16 gk2HolyWaterLockupSignature[] = {
3605 SIG_MAGICDWORD,
3606 0x38, SIG_SELECTOR16(has), // pushi has
3607 0x78, // push1
3608 0x39, 0x3e, // pushi 3e
3609 0x81, 0x00, // lag 00
3610 0x4a, SIG_UINT16(0x0006), // send 06 [ GraceEgo has: 62 (invBottleOfWater) ]
3611 SIG_END
3612 };
3613
3614 static const uint16 gk2HolyWaterLockupPatch[] = {
3615 0x38, PATCH_UINT16(0x0001), // pushi 0001
3616 0x38, PATCH_UINT16(0x0476), // pushi 0476
3617 0x47, 0x0b, 0x00, // calle proc11_0 [ is flag 1142 set? ]
3618 PATCH_UINT16(0x0002),
3619 PATCH_END
3620 };
3621
3622 // In early versions of GK2, Neuschwanstein castle can flash when clicking the
3623 // Hint button even after completing everything. The Hint script tests too many
3624 // flags including one whose value is random since it toggles back and forth
3625 // between two tape messages. We remove these flag tests as Sierra did.
3626 //
3627 // Applies to: English PC 1.0, Mac
3628 // Responsible method: local procedure #0 in script 800
3629 static const uint16 gk2NeuschwansteinHintSignature1[] = {
3630 SIG_MAGICDWORD,
3631 0x78, // push1
3632 0x38, SIG_UINT16(0x024d), // pushi 024d
3633 0x47, 0x0b, 0x00, // calle proc11_0 [ is flag 589 set? ]
3634 SIG_UINT16(0x0002),
3635 SIG_END
3636 };
3637
3638 static const uint16 gk2NeuschwansteinHintSignature2[] = {
3639 SIG_MAGICDWORD,
3640 0x78, // push1
3641 0x38, SIG_UINT16(0x024e), // pushi 024e
3642 0x47, 0x0b, 0x00, // calle proc11_0 [ is flag 590 set? ]
3643 SIG_UINT16(0x0002),
3644 SIG_END
3645 };
3646
3647 static const uint16 gk2NeuschwansteinHintSignature3[] = {
3648 SIG_MAGICDWORD,
3649 0x78, // push1
3650 0x38, SIG_UINT16(0x0250), // pushi 0250
3651 0x47, 0x0b, 0x00, // calle proc11_0 [ is flag 592 set? ]
3652 SIG_UINT16(0x0002),
3653 SIG_END
3654 };
3655
3656 static const uint16 gk2NeuschwansteinHintPatch[] = {
3657 0x35, 0x01, // ldi 01
3658 0x33, 0x05, // jmp 05
3659 PATCH_END
3660 };
3661
3662 // Clicking an inventory item on the Wagner paintings in rooms 8616 and 8617
3663 // causes a missing message error. The paintings only have responses for the
3664 // "Do" verb but painting:doVerb passes the incoming verb to gk2Messager:say
3665 // without any filtering. We fix this by always playing the "Do" message.
3666 //
3667 // Applies to: All versions
3668 // Responsible methods: painting:doVerb in scripts 8616 and 8617
3669 static const uint16 gk2WagnerPaintingMessageSignature[] = {
3670 SIG_MAGICDWORD,
3671 0x38, SIG_SELECTOR16(say), // pushi say
3672 0x38, SIG_UINT16(0x0006), // pushi 0006
3673 0x67, SIG_ADDTOOFFSET(+1), // pTos noun
3674 0x8f, 0x01, // lsp 01 [ verb ]
3675 SIG_END
3676 };
3677
3678 static const uint16 gk2WagnerPaintingMessagePatch[] = {
3679 PATCH_ADDTOOFFSET(+8),
3680 0x39, 0x3e, // pushi 3e [ "Do" verb ]
3681 PATCH_END
3682 };
3683
3684 // The game-over rooms 665 and 666 draw a pic over everything by setting the
3685 // default plane's priority to 202, but this is already inventoryBorderPlane's
3686 // priority. In our interpreter this causes a border fragment to be drawn above
3687 // the pics. This worked by luck in Sierra's interpreter because it sorts on
3688 // memory ID when planes have the same priority. In ScummVM the renderer
3689 // guarantees a sort order based on the creation order of the planes. The
3690 // default plane is created first and drawn before inventoryBorderPlane.
3691 //
3692 // We fix this by increasing the plane priority in the game-over rooms.
3693 //
3694 // Applies to: All versions
3695 // Responsible methods: gabeNews:init, uDie:init
3696 // Fixes bug: #11298
3697 static const uint16 gk2GameOverPrioritySignature[] = {
3698 0x39, SIG_SELECTOR8(priority), // pushi priority
3699 SIG_MAGICDWORD,
3700 0x78, // push1
3701 0x38, SIG_UINT16(0x00ca), // pushi 00ca
3702 0x81, 0x03, // lag 03
3703 0x4a, SIG_UINT16(0x0012), // send 12 [ Plane ... priority: 202 ]
3704 SIG_END
3705 };
3706
3707 static const uint16 gk2GameOverPriorityPatch[] = {
3708 PATCH_ADDTOOFFSET(+3),
3709 0x38, PATCH_UINT16(0x00cb), // pushi 00cb [ priority: 203 ]
3710 PATCH_END
3711 };
3712
3713 // GK2 fans have created patches that add subtitles to the entire game. There
3714 // are at least English and Spanish patch sets. Sierra added the subtitle
3715 // feature solely for the Portuguese version. The fan patches include these
3716 // subtitle scripts, replace the Portuguese resources and embedded script
3717 // strings, and configure Sierra's interpreter to use the Portuguese language
3718 // through RESOURCE.CFG. This sets GK2:printLang which the scripts test for
3719 // Portuguese in order to activate subtitles.
3720 //
3721 // The subtitle patches are compatible with ScummVM except for the requirement
3722 // that GK2:printLang equals Portuguese (351) since we don't use RESOURCE.CFG.
3723 // We fix this by patching the GK2:printLang tests to always activate subtitles
3724 // when a sync resource is present for synchronizing text to video playback.
3725 //
3726 // Applies to: PC versions with a subtitle fan-patch applied
3727 // Responsible methods: Any that test GK2:printLang for Portuguese
3728 // Fixes bugs: #9677, #11282
3729 static const uint16 gk2SubtitleCompatibilitySignature[] = {
3730 SIG_MAGICDWORD,
3731 0x39, SIG_SELECTOR8(printLang), // pushi printLang
3732 0x76, // push0
3733 0x81, 0x01, // lag 01
3734 0x4a, SIG_UINT16(0x0004), // send 04 [ GK2 printLang? ]
3735 SIG_END
3736 };
3737
3738 static const uint16 gk2SubtitleCompatibilityPatch[] = {
3739 0x34, PATCH_UINT16(0x015f), // ldi 015f [ K_LANG_PORTUGUESE ]
3740 0x33, 0x03, // jmp 03
3741 PATCH_END
3742 };
3743
3744 // script, description, signature patch
3745 static const SciScriptPatcherEntry gk2Signatures[] = {
3746 { true, 0, "disable volume reset on startup", 1, gk2VolumeResetSignature, gk2VolumeResetPatch },
3747 { true, 0, "disable video benchmarking", 1, gk2BenchmarkSignature, gk2BenchmarkPatch },
3748 { true, 23, "fix inventory scroll speed", 2, gk2InventoryScrollSpeedSignature, gk2InventoryScrollSpeedPatch },
3749 { true, 23, "fix inventory scroll direction", 1, gk2InventoryScrollDirSignature1, gk2InventoryScrollDirPatch1 },
3750 { true, 23, "fix inventory scroll direction (no line numbers)", 1, gk2InventoryScrollDirSignature2, gk2InventoryScrollDirPatch2 },
3751 { true, 37, "fix sound manager lockup", 1, gk2SoundManagerLockupSignature1, gk2SoundManagerLockupPatch1 },
3752 { true, 37, "fix sound manager lockup (no line numbers)", 1, gk2SoundManagerLockupSignature2, gk2SoundManagerLockupPatch2 },
3753 { true, 665, "fix game-over priority", 1, gk2GameOverPrioritySignature, gk2GameOverPriorityPatch },
3754 { true, 666, "fix game-over priority", 1, gk2GameOverPrioritySignature, gk2GameOverPriorityPatch },
3755 { true, 800, "fix neuschwanstein hint (1/3)", 1, gk2NeuschwansteinHintSignature1, gk2NeuschwansteinHintPatch },
3756 { true, 800, "fix neuschwanstein hint (2/3)", 1, gk2NeuschwansteinHintSignature2, gk2NeuschwansteinHintPatch },
3757 { true, 800, "fix neuschwanstein hint (3/3)", 1, gk2NeuschwansteinHintSignature3, gk2NeuschwansteinHintPatch },
3758 { true, 810, "fix frau miller lockup", 1, gk2FrauMillerLockupSignature, gk2FrauMillerLockupPatch },
3759 { true, 1020, "fix holy water lockup", 2, gk2HolyWaterLockupSignature, gk2HolyWaterLockupPatch },
3760 { true, 3210, "fix police station deadend", 1, gk2PoliceStationDeadendSignature, gk2PoliceStationDeadendPatch },
3761 { true, 4320, "fix xaver black wolf topic", 1, gk2XaverBlackWolfSignature, gk2XaverBlackWolfkPatch },
3762 { true, 8520, "fix georg letter topic", 1, gk2GeorgLetterTopicSignature, gk2GeorgLetterTopicPatch },
3763 { true, 8616, "fix wagner painting message", 2, gk2WagnerPaintingMessageSignature, gk2WagnerPaintingMessagePatch },
3764 { true, 8617, "fix wagner painting message", 2, gk2WagnerPaintingMessageSignature, gk2WagnerPaintingMessagePatch },
3765 { true, 64990, "increase number of save games (1/2)", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
3766 { true, 64990, "increase number of save games (2/2)", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
3767 { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
3768 { false, 0, "subtitle patch compatibility", 3, gk2SubtitleCompatibilitySignature, gk2SubtitleCompatibilityPatch },
3769 { false, 11, "subtitle patch compatibility", 7, gk2SubtitleCompatibilitySignature, gk2SubtitleCompatibilityPatch },
3770 { false, 12, "subtitle patch compatibility", 5, gk2SubtitleCompatibilitySignature, gk2SubtitleCompatibilityPatch },
3771 { false, 91, "subtitle patch compatibility", 7, gk2SubtitleCompatibilitySignature, gk2SubtitleCompatibilityPatch },
3772 { false, 200, "subtitle patch compatibility", 1, gk2SubtitleCompatibilitySignature, gk2SubtitleCompatibilityPatch },
3773 { false, 1300, "subtitle patch compatibility", 1, gk2SubtitleCompatibilitySignature, gk2SubtitleCompatibilityPatch },
3774 { false, 64924, "subtitle patch compatibility", 1, gk2SubtitleCompatibilitySignature, gk2SubtitleCompatibilityPatch },
3775 SCI_SIGNATUREENTRY_TERMINATOR
3776 };
3777
3778 #endif
3779
3780 // When spotting the destroyer, timing problems prevent completing the bridge
3781 // scene at fast game speeds.
3782 //
3783 // In the control room, room 25, ego and the captain go to the bridge, room 28,
3784 // where they spot ships in an effectively automatic scene. When this completes
3785 // they return to the control room, the captain falls, and the player regains
3786 // control of ego and has to walk to the control panel. This entire sequence
3787 // has to be completed within 400 game cycles or the destroyer kills the sub,
3788 // but the bridge timing is in wall time and has at least 25 seconds of delays,
3789 // which at faster speeds is longer than 400 cycles. The bridge also animates
3790 // during messages, causing the timer to run while reading, so even at slower
3791 // speeds the game can illogically end before the ships are revealed.
3792 //
3793 // There are several problems here but the real bug is that the timer starts
3794 // before the player has control. We fix this by disabling the timer during the
3795 // bridge and resetting it to 120 game cycles when the player regains control.
3796 // This preserves the original timer duration in the control room, where the
3797 // real timed action is, and is compatible with existing saved games. When the
3798 // timer expires, subMarineScript:changeState(9) no longer ends the game if
3799 // subMarine:roomFlags flag 2 isn't set, which captainfallsScript sets at the
3800 // same time that it now calls subMarineScript:changeState(8).
3801 //
3802 // Applies to: All versions
3803 // Responsible methods: subMarineScript:changeState, captainfallsScript:changeState
3804 // Fixes bug #11017
3805 static const uint16 icemanDestroyerTimer1Signature[] = {
3806 0x30, SIG_UINT16(0x0022), // bnt 0022 [ state 8 ]
3807 SIG_ADDTOOFFSET(+0x1f),
3808 SIG_MAGICDWORD,
3809 0x32, SIG_UINT16(0x0074), // jmp 0074 [ end of method ]
3810 0x3c, // dup
3811 0x35, 0x08, // ldi 08
3812 0x1a, // eq?
3813 0x30, SIG_UINT16(0x0008), // bnt 0008 [ state 9 ]
3814 0x34, SIG_UINT16(0x0190), // ldi 0190
3815 0x65, 0x10, // aTop cycles [ cycles = 400 ]
3816 0x32, SIG_UINT16(0x0065), // jmp 0065 [ end of method ]
3817 0x3c, // dup
3818 0x35, 0x09, // ldi 09
3819 0x1a, // eq?
3820 0x30, SIG_UINT16(0x0023), // bnt 0023 [ state 15 ]
3821 0x8f, 0x00, // lsp 00
3822 0x35, 0x02, // ldi 02
3823 0x22, // lt? [ didn't reach control panel? ]
3824 0x30, SIG_UINT16(0x0014), // bnt 0014 [ skip death if reached control panel ]
3825 SIG_END
3826 };
3827
3828 static const uint16 icemanDestroyerTimer1Patch[] = {
3829 0x30, PATCH_UINT16(0x001f), // bnt 001f [ state 8 ]
3830 PATCH_ADDTOOFFSET(+0x1f),
3831 0x3c, // dup
3832 0x35, 0x08, // ldi 08
3833 0x1a, // eq?
3834 0x31, 0x04, // bnt 04 [ state 9 ]
3835 0x35, 0x78, // ldi 78
3836 0x65, 0x10, // aTop cycles [ cycles = 120 ]
3837 0x3c, // dup
3838 0x35, 0x09, // ldi 09
3839 0x1a, // eq?
3840 0x31, 0x2c, // bnt 2c [ state 15 ]
3841 0x38, PATCH_SELECTOR16(roomFlags), // pushi roomFlags
3842 0x76, // push0
3843 0x63, 0x08, // pToa client
3844 0x4a, 0x04, // send 04 [ subMarine roomFlags? ]
3845 0x7a, // push2 [ flag 2 set when captain falls ]
3846 0x12, // and [ has captain fallen? ]
3847 0x31, 0x19, // bnt 19 [ skip death if captain hasn't fallen ]
3848 0x8f, 0x00, // lsp 00
3849 0x22, // lt? [ didn't reach control panel? ]
3850 0x31, 0x14, // bnt 14 [ skip death if reached control panel ]
3851 PATCH_END
3852 };
3853
3854 static const uint16 icemanDestroyerTimer2Signature[] = {
3855 // print four messages
3856 0x7a, // push2
3857 0x38, SIG_UINT16(0x0187), // pushi 0187
3858 SIG_MAGICDWORD,
3859 0x7a, // push2
3860 0x47, 0xff, 0x00, 0x04, // calle proc255_0 [ print 391 2 ]
3861 SIG_ADDTOOFFSET(+20), // [ print 391 3, print 391 4 ]
3862 0x7a, // push2
3863 0x38, SIG_UINT16(0x0187), // pushi 0187
3864 0x39, 0x05, // pushi 05
3865 0x47, 0xff, 0x00, 0x04, // calle proc255_0 [ print 391 5 ]
3866 SIG_END
3867 };
3868
3869 static const uint16 icemanDestroyerTimer2Patch[] = {
3870 // print four messages using a loop
3871 0x35, 0x02, // ldi 02
3872 0xa7, 0x01, // sap 01
3873 0x8f, 0x01, // lsp 01
3874 0x35, 0x05, // ldi 05
3875 0x24, // le? [ loop while 2 <= param1 <= 5 ]
3876 0x31, 0x0e, // bnt 0e [ exit loop ]
3877 0x7a, // push2
3878 0x38, PATCH_UINT16(0x0187), // pushi 0187
3879 0x8f, 0x01, // lsp 01
3880 0x47, 0xff, 0x00, 0x04, // calle proc255_0 [ print 391 param1 ]
3881 0xcf, 0x01, // +sp 01 [ increment and push param1 ]
3882 0x33, 0xed, // jmp ed [ continue loop ]
3883 // reset subMarineScript timer
3884 0x39, PATCH_SELECTOR8(script), // pushi script
3885 0x76, // push0
3886 0x51, 0x5c, // class subMarine
3887 0x4a, 0x04, // send 04 [ subMarine script? ]
3888 0x39, PATCH_SELECTOR8(changeState), // pushi changeState
3889 0x78, // push1
3890 0x39, 0x08, // pushi 08
3891 0x4a, 0x06, // send 06 [ subMarineScript changeState: 8 ]
3892 PATCH_END
3893 };
3894
3895 // At the pier in Honolulu, room 23, "climb down" causes ego to bypass boarding
3896 // procedure, walk through the air, climb down the hatch, and get stuck in the
3897 // submarine without triggering a room change. There is no "climb up" command.
3898 //
3899 // Boarding requires asking the officer permission. comeAboardScript gives him
3900 // the orders, runs downTheHatchScript, and changes to room 31 when finished.
3901 // downTheHatchScript only walks ego to the hatch and runs the climb animation.
3902 // "climb down" simply runs downTheHatchScript and nothing else, leaving the
3903 // room in a broken state by running this intermediate script out of context.
3904 //
3905 // We patch "climb down" to respond with the message for other hatch commands.
3906 //
3907 // Applies to: All versions
3908 // Responsible method: hatch:handleEvent
3909 // Fixes bug #11039
3910 static const uint16 icemanClimbDownHatchSignature[] = {
3911 0x7a, // push2
3912 SIG_MAGICDWORD,
3913 0x39, 0x17, // pushi 17
3914 0x39, 0x18, // pushi 18
3915 0x47, 0xff, 0x00, 0x04, // calle proc255_0 04 [ "You must follow proper boarding procedure." ]
3916 0x32, SIG_UINT16(0x0021), // jmp 0021 [ end of method ]
3917 SIG_ADDTOOFFSET(+22),
3918 0x39, SIG_SELECTOR8(setScript), // pushi setScript
3919 0x78, // push1
3920 0x72, SIG_UINT16(0xfc24), // lofsa downTheHatchScript
3921 SIG_END
3922 };
3923
3924 static const uint16 icemanClimbDownHatchPatch[] = {
3925 PATCH_ADDTOOFFSET(+34),
3926 0x33, 0xdc, // jmp dc [ "You must follow proper boarding procedure." ]
3927 PATCH_END
3928 };
3929
3930 // script, description, signature patch
3931 static const SciScriptPatcherEntry icemanSignatures[] = {
3932 { true, 23, "climb down hatch", 1, icemanClimbDownHatchSignature, icemanClimbDownHatchPatch },
3933 { true, 314, "destroyer timer (1/2)", 1, icemanDestroyerTimer1Signature, icemanDestroyerTimer1Patch },
3934 { true, 391, "destroyer timer (2/2)", 1, icemanDestroyerTimer2Signature, icemanDestroyerTimer2Patch },
3935 SCI_SIGNATUREENTRY_TERMINATOR
3936 };
3937
3938 // ===========================================================================
3939 // At least during the harpy scene, export 29 of script 0 is called and has an
3940 // issue where temp[3] won't get inititialized, but is later used to set
3941 // master volume. This makes SSCI set the volume to max. We fix the procedure,
3942 // so volume won't get modified in those cases.
3943 //
3944 // Applies to at least: PC CD
3945 // Responsible method: export 29 in script 0
3946 // Fixes bug: #5209
3947 static const uint16 kq5SignatureCdHarpyVolume[] = {
3948 SIG_MAGICDWORD,
3949 0x80, SIG_UINT16(0x0191), // lag global[191h]
3950 0x18, // not
3951 0x30, SIG_UINT16(0x002c), // bnt [jump further] (jumping, if global[191h] is 1)
3952 0x35, 0x01, // ldi 01
3953 0xa0, SIG_UINT16(0x0191), // sag global[191h] (setting to 1)
3954 0x38, SIG_UINT16(0x017b), // pushi 017b
3955 0x76, // push0
3956 0x81, 0x01, // lag global[1]
3957 0x4a, 0x04, // send 04 - read KQ5::masterVolume
3958 0xa5, 0x03, // sat temp[3] (store volume)
3959 0x38, SIG_UINT16(0x017b), // pushi 017b
3960 0x76, // push0
3961 0x81, 0x01, // lag global[1]
3962 0x4a, 0x04, // send 04 - read KQ5::masterVolume
3963 0x36, // push
3964 0x35, 0x04, // ldi 04
3965 0x20, // ge? (followed by bnt)
3966 SIG_END
3967 };
3968
3969 static const uint16 kq5PatchCdHarpyVolume[] = {
3970 0x38, PATCH_UINT16(0x022f), // pushi 022f (selector theVol) (3 new bytes)
3971 0x76, // push0 (1 new byte)
3972 0x51, 0x88, // class SpeakTimer (2 new bytes)
3973 0x4a, 0x04, // send 04 (2 new bytes) -> read SpeakTimer::theVol
3974 0xa5, 0x03, // sat temp[3] (2 new bytes)
3975 0x80, PATCH_UINT16(0x0191), // lag global[191h]
3976 // saving 1 byte due optimization
3977 0x2e, PATCH_UINT16(0x0023), // bt [jump further] (jumping, if global[191h] is 1)
3978 0x35, 0x01, // ldi 01
3979 0xa0, PATCH_UINT16(0x0191), // sag global[191h] (setting to 1)
3980 0x38, PATCH_UINT16(0x017b), // pushi 017b
3981 0x76, // push0
3982 0x81, 0x01, // lag global[1]
3983 0x4a, 0x04, // send 04 - read KQ5::masterVolume
3984 0xa5, 0x03, // sat temp[3] (store volume)
3985 // saving 8 bytes due removing of duplicate code
3986 0x39, 0x04, // pushi 04 (saving 1 byte due swapping)
3987 0x22, // lt? (because we switched values)
3988 PATCH_END
3989 };
3990
3991 // The witchCage object in script 200 is broken and claims to have 12
3992 // properties instead of the 8 it should have because it is a Cage.
3993 // Additionally its top,left,bottom,right properties are set to 0 rather
3994 // than the right values. We fix the object by setting the right values.
3995 // If they are all zero, this causes an impossible position check in
3996 // witch::cantBeHere and an infinite loop when entering room 22.
3997 //
3998 // This bug is accidentally not triggered in SSCI because the invalid number
3999 // of variables effectively hides witchCage::doit, causing this position check
4000 // to be bypassed entirely.
4001 // See also the warning+comment in Object::initBaseObject
4002 //
4003 // Applies to at least: PC CD, English PC floppy
4004 // Responsible method: heap in script 200
4005 // Fixes bug: #4964
4006 static const uint16 kq5SignatureWitchCageInit[] = {
4007 SIG_UINT16(0x0000), // top
4008 SIG_UINT16(0x0000), // left
4009 SIG_UINT16(0x0000), // bottom
4010 SIG_UINT16(0x0000), // right
4011 SIG_UINT16(0x0000), // extra property #1
4012 SIG_MAGICDWORD,
4013 SIG_UINT16(0x007a), // extra property #2
4014 SIG_UINT16(0x00c8), // extra property #3
4015 SIG_UINT16(0x00a3), // extra property #4
4016 SIG_END
4017 };
4018
4019 static const uint16 kq5PatchWitchCageInit[] = {
4020 PATCH_UINT16(0x0000), // top
4021 PATCH_UINT16(0x007a), // left
4022 PATCH_UINT16(0x00c8), // bottom
4023 PATCH_UINT16(0x00a3), // right
4024 PATCH_END
4025 };
4026
4027 // The multilingual releases of KQ5 hang right at the end during the magic
4028 // battle with Mordack. It seems additional code was added to wait for signals,
4029 // but the signals are never set and thus the game hangs. We disable that code,
4030 // so that the battle works again. This also happened in the original
4031 // interpreter. We must not change similar code, that happens before.
4032 //
4033 // Applies to at least: French PC floppy, German PC floppy, Spanish PC floppy
4034 // Responsible method: stingScript::changeState, dragonScript::changeState, snakeScript::changeState
4035 static const uint16 kq5SignatureMultilingualEndingGlitch[] = {
4036 SIG_MAGICDWORD,
4037 0x89, 0x57, // lsg global[57h]
4038 0x35, 0x00, // ldi 0
4039 0x1a, // eq?
4040 0x18, // not
4041 0x30, SIG_UINT16(0x0011), // bnt [skip signal check]
4042 SIG_ADDTOOFFSET(+8), // skip globalSound::prevSignal get code
4043 0x36, // push
4044 0x35, 0x0a, // ldi 0Ah
4045 SIG_END
4046 };
4047
4048 static const uint16 kq5PatchMultilingualEndingGlitch[] = {
4049 PATCH_ADDTOOFFSET(+6),
4050 0x32, // jmp (replace bnt)
4051 PATCH_END
4052 };
4053
4054 // In the final battle, the DOS version uses signals in the music to handle
4055 // timing, while in the Windows version another method is used and the GM
4056 // tracks do not contain these signals. The original kq5 interpreter used
4057 // global[400] to distinguish between Windows (1) and DOS (0) versions.
4058 //
4059 // We replace the global[400] checks with a fixed true. This is toggled with
4060 // enablePatch() below when alternative Windows GM MIDI tracks are used.
4061 //
4062 // Instead, we could have set global[400], but this has the possibly unwanted
4063 // side effects of switching to black&white cursors (which also needs complex
4064 // changes to GameFeatures::detectsetCursorType()) and breaking savegame
4065 // compatibilty between the DOS and Windows CD versions of KQ5.
4066 //
4067 // TODO: Investigate those side effects more closely.
4068 // Applies to at least: Win CD
4069 // Responsible method: mordOneScript::changeState(1), dragonScript::changeState(1),
4070 // fireScript::changeState() in script 124
4071 // Fixes bug: #6251
4072 static const uint16 kq5SignatureWinGMSignals[] = {
4073 SIG_MAGICDWORD,
4074 0x80, SIG_UINT16(0x0190), // lag global[400]
4075 0x18, // not
4076 0x30, SIG_UINT16(0x001b), // bnt 0x001b
4077 0x89, 0x57, // lsg global[87]
4078 SIG_END
4079 };
4080
4081 static const uint16 kq5PatchWinGMSignals[] = {
4082 0x34, PATCH_UINT16(0x0001), // ldi 0x0001
4083 PATCH_END
4084 };
4085
4086 // script, description, signature patch
4087 static const SciScriptPatcherEntry kq5Signatures[] = {
4088 { true, 0, "CD: harpy volume change", 1, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume },
4089 { true, 124, "Multilingual: Ending glitching out", 3, kq5SignatureMultilingualEndingGlitch, kq5PatchMultilingualEndingGlitch },
4090 { false, 124, "Win: GM Music signal checks", 4, kq5SignatureWinGMSignals, kq5PatchWinGMSignals },
4091 { true, 200, "CD: witch cage init", 1, kq5SignatureWitchCageInit, kq5PatchWitchCageInit },
4092 SCI_SIGNATUREENTRY_TERMINATOR
4093 };
4094
4095 // ===========================================================================
4096 // In the garden (room 480), when giving the milk bottle to one of the babies,
4097 // script 481 starts a looping a baby cry sound (cryMusic). However, that
4098 // particular sound has an overriden check() method that explicitly restarts
4099 // the sound, even if it's set to be looped. Thus the same sound is played
4100 // twice, squelching all other sounds.
4101 //
4102 // We just rip out the unnecessary check() method, thereby stopping the sound
4103 // from constantly restarting (since it's being looped anyway), thus the normal
4104 // game speech can work while the baby cry sound is heard.
4105 //
4106 // Applies to at least: PC-CD
4107 // Responsible method: cryMusic::check in script 481
4108 // Fixes bug: #4955
4109 static const uint16 kq6SignatureDuplicateBabyCry[] = {
4110 SIG_MAGICDWORD,
4111 0x83, 0x00, // lal local[0]
4112 0x31, 0x1e, // bnt 1e [07f4]
4113 0x78, // push1
4114 0x39, 0x04, // pushi 04
4115 0x43, 0x75, 0x02, // callk DoAudio[75], 02
4116 SIG_END
4117 };
4118
4119 static const uint16 kq6PatchDuplicateBabyCry[] = {
4120 0x48, // ret
4121 PATCH_END
4122 };
4123
4124 // The inventory of King's Quest 6 is buggy. When it grows too large,
4125 // it will get split into 2 pages. Switching between those pages will
4126 // grow the stack, because it's calling itself per switch.
4127 // Which means after a while ScummVM will bomb out because the stack frame
4128 // will be too large. This patch fixes the buggy script.
4129 //
4130 // Applies to at least: PC-CD, English PC floppy, German PC floppy, English Mac
4131 // Responsible method: KqInv::showSelf in script 907
4132 // Fixes bug: #5681
4133 static const uint16 kq6SignatureInventoryStackFix[] = {
4134 0x67, 0x30, // pTos state
4135 0x34, SIG_UINT16(0x2000), // ldi 2000
4136 0x12, // and
4137 0x18, // not
4138 0x31, 0x04, // bnt [not first refresh]
4139 0x35, 0x00, // ldi 00
4140 SIG_MAGICDWORD,
4141 0x65, 0x1e, // aTop curIcon
4142 0x67, 0x30, // pTos state
4143 0x34, SIG_UINT16(0xdfff), // ldi dfff
4144 0x12, // and
4145 0x65, 0x30, // aTop state
4146 0x38, SIG_SELECTOR16(show), // pushi show (e1h for KQ6CD)
4147 0x78, // push1
4148 0x87, 0x00, // lap param[0]
4149 0x31, 0x04, // bnt [use global for show]
4150 0x87, 0x01, // lap param[1]
4151 0x33, 0x02, // jmp [use param for show]
4152 0x81, 0x00, // lag global[0]
4153 0x36, // push
4154 0x54, 0x06, // self 06 (KqInv::show)
4155 0x31, SIG_ADDTOOFFSET(+1), // bnt [exit menu code] (0x08 for PC, 0x07 for mac)
4156 0x39, 0x39, // pushi 39
4157 0x76, // push0
4158 0x54, 0x04, // self 04 (KqInv::doit)
4159 SIG_END // followed by jmp (0x32 for PC, 0x33 for mac)
4160 };
4161
4162 static const uint16 kq6PatchInventoryStackFix[] = {
4163 0x67, 0x30, // pTos state
4164 0x3c, // dup (1 more byte, needed for patch)
4165 0x3c, // dup (1 more byte, saves 1 byte later)
4166 0x34, PATCH_UINT16(0x2000), // ldi 2000
4167 0x12, // and
4168 0x2f, 0x02, // bt [not first refresh] - saves 3 bytes in total
4169 0x65, 0x1e, // aTop curIcon
4170 0x00, // bnot (neg, either 2000 or 0000 in acc, this will create dfff or ffff) - saves 2 bytes
4171 0x12, // and
4172 0x65, 0x30, // aTop state
4173 0x38, PATCH_GETORIGINALUINT16(+22), // pushi show
4174 0x78, // push1
4175 0x87, 0x00, // lap param[0]
4176 0x31, 0x04, // bnt [call show using global[0]]
4177 0x8f, 0x01, // lsp param[1], save 1 byte total with lsg global[0] combined
4178 0x33, 0x02, // jmp [call show using param[1]]
4179 0x89, 0x00, // lsg global[0], save 1 byte total, see above
4180 0x54, 0x06, // self 06 (call x::show)
4181 0x31, PATCH_GETORIGINALBYTEADJUST(+39, +6), // bnt [menu exit code] (0x0e for pc, 0x0d for mac)
4182 0x34, PATCH_UINT16(0x2000), // ldi 2000
4183 0x12, // and
4184 0x2f, 0x05, // bt [to ret]
4185 0x39, 0x39, // pushi 39
4186 0x76, // push0
4187 0x54, 0x04, // self 04 (self::doit)
4188 0x48, // ret (saves 2 bytes for PC, 1 byte for mac)
4189 PATCH_END
4190 };
4191
4192 // The "Drink Me" bottle code doesn't repaint the AddToPics elements to the
4193 // screen, when Alexander returns back from the effect of the bottle.
4194 // It's pretty strange that Sierra didn't find this bug, because it occurs
4195 // when drinking the bottle right on the screen, where the bottle is found.
4196 // This bug also occurs in Sierra SCI.
4197 //
4198 // Applies to at least: PC-CD, English PC floppy, German PC floppy, English Mac
4199 // Responsible method: drinkMeScript::changeState in script 87
4200 // Fixes bug: #5252
4201 static const uint16 kq6SignatureDrinkMeFix[] = {
4202 SIG_MAGICDWORD,
4203 0x3c, // dup
4204 0x35, 0x0f, // ldi 0f
4205 0x1a, // eq?
4206 0x30, SIG_UINT16(0x00a4), // bnt [skip to next check]
4207 SIG_ADDTOOFFSET(+161),
4208 0x32, SIG_UINT16(0x007f), // jmp [return]
4209 0x3c, // dup
4210 0x35, 0x10, // ldi 10
4211 0x1a, // eq?
4212 0x31, 0x07, // bnt [skip to next check]
4213 0x35, 0x03, // ldi 03
4214 0x65, 0x1a, // aTop (cycles)
4215 0x32, SIG_UINT16(0x0072), // jmp [return]
4216 0x3c, // dup
4217 0x35, 0x11, // ldi 11
4218 0x1a, // eq?
4219 0x31, 0x13, // bnt [skip to next check]
4220 SIG_ADDTOOFFSET(+20),
4221 0x35, 0x12, // ldi 12
4222 SIG_ADDTOOFFSET(+23),
4223 0x35, 0x13, // ldi 13
4224 SIG_END
4225 };
4226
4227 static const uint16 kq6PatchDrinkMeFix[] = {
4228 PATCH_ADDTOOFFSET(+4),
4229 0x30, PATCH_UINT16(0x00b1), // bnt 00b1 [ check for 11h code ]
4230 PATCH_ADDTOOFFSET(+161),
4231 0x39, PATCH_SELECTOR8(doit), // pushi doit
4232 0x76, // push0
4233 0x81, 0x0a, // lag global[0a]
4234 0x4a, 0x04, // send 04 (call addToPics::doit)
4235 0x3a, // toss
4236 0x48, // ret
4237 PATCH_ADDTOOFFSET(+8), // skip to check 11h code
4238 0x35, 0x10, // ldi 10 (instead of 11)
4239 PATCH_ADDTOOFFSET(+23), // skip to check 12h code
4240 0x35, 0x11, // ldi 11 (instead of 12)
4241 PATCH_ADDTOOFFSET(+23), // skip to check 13h code
4242 0x35, 0x12, // ldi 12 (instead of 13)
4243 PATCH_END
4244 };
4245
4246 // KQ6 Mac is missing pic 981 and crashes when drinking the "Drink Me" bottle.
4247 // This also crashed the original. Pic 981 is a black background and it's only
4248 // used in this scene. Pic 98 is also a black background and used when the game
4249 // starts and everywhere else. We restore this scene by switching to pic 98.
4250 //
4251 // This patch is only enabled on Mac as the script is the same in all versions.
4252 // The pics have different palettes and applying it to PC would disable the
4253 // palette cycling between red and black during this scene. KQ6 Mac didn't do
4254 // these palette cycling effects as it didn't include any palette resources.
4255 //
4256 // Applies to: English Mac
4257 // Responsible method: drinkMeScript:changeState(0)
4258 static const uint16 kq6SignatureMacDrinkMePic[] = {
4259 SIG_MAGICDWORD,
4260 0x38, SIG_UINT16(0x03d5), // pushi 981d
4261 0x39, 0x09, // pushi 09
4262 0x43, 0x08, 0x04, // callk DrawPic 04
4263 SIG_END
4264 };
4265
4266 static const uint16 kq6PatchMacDrinkMePic[] = {
4267 0x38, PATCH_UINT16(0x0062), // pushi 98d
4268 PATCH_END
4269 };
4270
4271 // During the common Game Over cutscene, one of the guys says "Tickets, only",
4272 // but the subtitle says "Tickets, please". Normally people wouldn't have
4273 // noticed, but ScummVM supports audio + subtitles in this game at the same
4274 // time. This is caused by a buggy message, which really has this text + audio
4275 // attached.
4276 // We assume that "Tickets, only" (the audio) is the correct one. There is
4277 // another message with "Tickets, only" in both text and audio. We change
4278 // message 1, 0, 1, 1 to message 5, 0, 0, 2 to fix this issue.
4279 // This mismatch also occurs in Sierra SCI.
4280 //
4281 // Applies to at least: PC-CD
4282 // Responsible method: modeLessScript::changeState(0) in script 640
4283 static const uint16 kq6SignatureTicketsOnly[] = {
4284 0x3c, // dup
4285 0x35, 0x00, // ldi 0
4286 0x1a, // eq?
4287 SIG_MAGICDWORD,
4288 0x31, 0x2b, // bnt [skip over state 0]
4289 0x39, 0x1e, // pushi font (we keep the hardcoded selectors in here simply because this is only for KQ6-CD)
4290 0x78, // push1
4291 0x89, 0x16, // lsg global[16h]
4292 0x38, SIG_UINT16(0x009a), // pushi posn
4293 0x7a, // push2
4294 0x38, SIG_UINT16(0x00c8), // pushi 00c8h (200d)
4295 0x39, 0x64, // pushi 64h (100d)
4296 0x38, SIG_UINT16(0x00ab), // pushi say
4297 0x39, 0x05, // pushi 05 (parameter count for say)
4298 0x76, // push0
4299 0x78, // push1
4300 0x76, // push0
4301 0x78, // push1
4302 0x78, // push1
4303 SIG_END
4304 };
4305
4306 static const uint16 kq6PatchTicketsOnly[] = {
4307 0x32, PATCH_UINT16(0x0000), // jmp (waste 3 bytes)
4308 0x2f, 0x2c, // bt [skip over state 0] (saves 1 byte)
4309 0x39, 0x1e, // pushi font (we keep the hardcoded selectors in here simply because this is only for KQ6-CD)
4310 0x78, // push1
4311 0x89, 0x16, // lsg global[16h]
4312 0x38, PATCH_UINT16(0x009a), // pushi posn
4313 0x7a, // push2
4314 0x38, PATCH_UINT16(0x00c8), // pushi 00c8h (200d)
4315 0x39, 0x64, // pushi 64h (100d)
4316 0x38, PATCH_UINT16(0x00ab), // pushi say
4317 0x39, 0x05, // pushi 05 (parameter count for say)
4318 0x76, // push0
4319 0x39, 0x05, // pushi 05
4320 0x76, // push0
4321 0x76, // push0
4322 0x7a, // push2
4323 PATCH_END
4324 };
4325
4326 // Looking at the ribbon in inventory says that there's a hair even after it's
4327 // been removed. This occurs after the hair has been put in the skull or is on
4328 // a different inventory page than the ribbon.
4329 //
4330 // The ribbon's Look handler has incorrect logic for determining if it contains
4331 // a hair. It fails to test flag 143 which is set when getting the hair and so
4332 // it displays the wrong message. The Do handler tests all the necessary flags.
4333 // This bug probably would have been noticed except that both verb handlers
4334 // also test inventory for hair, which is redundant as testing flags is enough,
4335 // but it causes the right message some of the time. Testing inventory is wrong
4336 // because possessing the hair is temporary, which is why the bug emerges after
4337 // it's used, and it's broken because testing inventory across pages doesn't
4338 // work in KQ6. ego:has returns false for any item on another page when the
4339 // inventory window is open. As inventory increases the ribbon and hair end up
4340 // on different pages and ribbon:doVerb can no longer see it.
4341 //
4342 // We fix the message by changing ribbon:doVerb(1) to test flag 143 like doVerb(5).
4343 // This requires overwriting one of the redundant inventory tests.
4344 //
4345 // Beauty's clothes also have a hair and clothes:doVerb(1) has similar issues
4346 // but it happens to work. Those items are always on the same page due to their
4347 // low item numbers and the clothes are removed from inventory before the hair.
4348 //
4349 // Applies to: PC CD, PC Floppy, Mac Floppy
4350 // Responsible method: ribbon:doVerb(1) in script 907
4351 // Fixes bug: #10801
4352 static const uint16 kq6SignatureLookRibbonFix[] = {
4353 0x30, SIG_ADDTOOFFSET(+2), // bnt [ verb != Look ]
4354 0x38, SIG_SELECTOR16(has), // pushi has
4355 0x78, // push1
4356 0x39, 0x04, // pushi 04
4357 0x81, SIG_MAGICDWORD, 0x00, // lag global[0]
4358 0x4a, 0x06, // send 6 [ ego:has 4 (beauty's hair) ]
4359 0x2e, // bt [ continue hair tests ]
4360 SIG_END
4361 };
4362
4363 static const uint16 kq6PatchLookRibbonFix[] = {
4364 PATCH_ADDTOOFFSET(+3),
4365 0x78, // push1
4366 0x38, PATCH_UINT16(0x008f), // pushi 008f
4367 0x46, PATCH_UINT16(0x0391), // calle [export 0 of script 13], 02 [ is flag 8f set? ]
4368 PATCH_UINT16(0x0000), 0x02,
4369 PATCH_END
4370 };
4371
4372 // KQ6 CD introduced a bug in the wallflower dance in room 480. The dance is
4373 // supposed to last until the music ends but in Text mode it stops after only
4374 // three seconds once the user gains control. This isn't usually enough time
4375 // to get the hole in the wall. This bug also occurs in Sierra's interpreter.
4376 //
4377 // wallFlowerDance was changed in the CD version for Speech mode but broke Text.
4378 // In Text mode, changeState(9) creates a dialog with Print, which blocks, and
4379 // then sets ticks to 12. Meanwhile, wallFlowerDance:handleEvent cues if an
4380 // event is received in state 9. A mouse click starts a 12 tick race which
4381 // handleEvent wins, cueing before the countdown expires, and so the countdown
4382 // expires on state 10, skipping ahead to the three second fadeout. Closing the
4383 // dialog with the keyboard works because Dialog claims keyboard events when
4384 // blocking, preventing wallFlowerDance:handleEvent from receiving and cueing.
4385 //
4386 // We fix this by setting the Print dialog to modeless as it was in the floppy
4387 // version and removing the countdown. wallFlowerDance:handleEvent now receives
4388 // all events and is the only one responsible for advancing state 9 to 10 in
4389 // Text mode. This patch does not affect audio modes Speech and Both.
4390 //
4391 // Applies to: PC CD
4392 // Responsible method: wallFlowerDance:changeState(9) in script 480
4393 // Fixes bug: #10811
4394 static const uint16 kq6CDSignatureWallFlowerDanceFix[] = {
4395 SIG_MAGICDWORD,
4396 0x39, SIG_SELECTOR8(init), // pushi init
4397 0x76, // push0
4398 0x51, 0x15, // class Print [ Print: ... init ]
4399 0x4a, 0x24, // send 24
4400 0x35, 0x0c, // ldi 0c
4401 0x65, 0x20, // aTop ticks
4402 0x32, SIG_UINT16(0x00d0), // jmp 00d0 [ end of method ]
4403 SIG_END
4404 };
4405
4406 static const uint16 kq6CDPatchWallFlowerDanceFix[] = {
4407 0x38, PATCH_SELECTOR16(modeless), // pushi modeless
4408 0x78, // push1
4409 0x78, // push1
4410 0x39, PATCH_SELECTOR8(init), // pushi init
4411 0x76, // push0
4412 0x51, 0x15, // class Print [ Print: ... modeless: 1, init ]
4413 0x4a, 0x2a, // send 2a
4414 0x3a, // toss
4415 0x48, // ret
4416 PATCH_END
4417 };
4418
4419 // Audio + subtitles support - SHARED! - used for King's Quest 6 and Laura Bow 2.
4420 // This patch gets enabled when the user selects "both" in the ScummVM
4421 // "Speech + Subtitles" menu. We currently use global[98d] to hold a kMemory
4422 // pointer.
4423 // Applies to at least: KQ6 PC-CD, LB2 PC-CD
4424 // Patched method: Messager::sayNext / lb2Messager::sayNext (always use text branch)
4425 static const uint16 kq6laurabow2CDSignatureAudioTextSupport1[] = {
4426 0x89, 0x5a, // lsg global[5a]
4427 0x35, 0x02, // ldi 02
4428 0x12, // and
4429 SIG_MAGICDWORD,
4430 0x31, 0x13, // bnt [audio call]
4431 0x38, SIG_SELECTOR16(modNum), // pushi modNum
4432 SIG_END
4433 };
4434
4435 static const uint16 kq6laurabow2CDPatchAudioTextSupport1[] = {
4436 PATCH_ADDTOOFFSET(+5),
4437 0x33, 0x13, // jmp [audio call]
4438 PATCH_END
4439 };
4440
4441 // Applies to at least: KQ6 PC-CD, LB2 PC-CD
4442 // Patched method: Messager::sayNext / lb2Messager::sayNext (allocate audio memory)
4443 static const uint16 kq6laurabow2CDSignatureAudioTextSupport2[] = {
4444 0x7a, // push2
4445 0x78, // push1
4446 0x39, 0x0c, // pushi 0c
4447 0x43, SIG_MAGICDWORD, 0x72, 0x04, // callk Memory
4448 0xa5, 0xc9, // sat temp[c9]
4449 SIG_END
4450 };
4451
4452 static const uint16 kq6laurabow2CDPatchAudioTextSupport2[] = {
4453 PATCH_ADDTOOFFSET(+7),
4454 0xa1, 0x62, // sag global[62] (global[98d])
4455 PATCH_END
4456 };
4457
4458 // Applies to at least: KQ6 PC-CD, LB2 PC-CD
4459 // Patched method: Messager::sayNext / lb2Messager::sayNext (release audio memory)
4460 static const uint16 kq6laurabow2CDSignatureAudioTextSupport3[] = {
4461 0x7a, // push2
4462 0x39, 0x03, // pushi 03
4463 SIG_MAGICDWORD,
4464 0x8d, 0xc9, // lst temp[c9]
4465 0x43, 0x72, 0x04, // callk Memory
4466 SIG_END
4467 };
4468
4469 static const uint16 kq6laurabow2CDPatchAudioTextSupport3[] = {
4470 PATCH_ADDTOOFFSET(+3),
4471 0x89, 0x62, // lsg global[62] (global[98d])
4472 PATCH_END
4473 };
4474
4475 // startText call gets acc = 0 for text-only and acc = 2 for audio+text
4476 // Applies to at least: KQ6 PC-CD, LB2 PC-CD
4477 // Patched method: Narrator::say (use audio memory)
4478 static const uint16 kq6laurabow2CDSignatureAudioTextSupport4[] = {
4479 // set caller property code
4480 0x31, 0x08, // bnt [set acc to 0 for caller]
4481 0x87, 0x02, // lap param[2]
4482 0x31, 0x04, // bnt [set acc to 0 for caller]
4483 0x87, 0x02, // lap param[2]
4484 0x33, 0x02, // jmp [set caller]
4485 0x35, 0x00, // ldi 00
4486 0x65, 0x68, // aTop caller
4487 // call startText + startAudio code
4488 0x89, 0x5a, // lsg global[5a]
4489 0x35, 0x01, // ldi 01
4490 0x12, // and
4491 0x31, 0x08, // bnt [skip code]
4492 0x38, SIG_SELECTOR16(startText), // pushi startText
4493 0x78, // push1
4494 0x8f, 0x01, // lsp param[1]
4495 0x54, 0x06, // self 06
4496 0x89, 0x5a, // lsg global[5a]
4497 0x35, 0x02, // ldi 02
4498 0x12, // and
4499 0x31, 0x08, // bnt [skip code]
4500 SIG_MAGICDWORD,
4501 0x38, SIG_SELECTOR16(startAudio), // pushi startAudio
4502 0x78, // push1
4503 0x8f, 0x01, // lsp param[1]
4504 0x54, 0x06, // self 06
4505 SIG_END
4506 };
4507
4508 static const uint16 kq6laurabow2CDPatchAudioTextSupport4[] = {
4509 0x31, 0x02, // bnt [set caller]
4510 0x87, 0x02, // lap param[2]
4511 0x65, 0x68, // aTop caller
4512 0x81, 0x5a, // lag global[5a]
4513 0x78, // push1
4514 0x12, // and
4515 0x31, 0x11, // bnt [skip startText code]
4516 0x81, 0x5a, // lag global[5a]
4517 0x7a, // push2
4518 0x12, // and
4519 0x33, 0x03, // jmp 3 [skip unused bytes]
4520 PATCH_ADDTOOFFSET(+22),
4521 0x89, 0x62, // lsg global[62] (global[98d])
4522 PATCH_END
4523 };
4524
4525 // Applies to at least: KQ6 PC-CD, LB2 PC-CD
4526 // Patched method: Talker::display/Narrator::say (remove reset saved mouse cursor code)
4527 // code would screw over mouse cursor
4528 static const uint16 kq6laurabow2CDSignatureAudioTextSupport5[] = {
4529 SIG_MAGICDWORD,
4530 0x35, 0x00, // ldi 00
4531 0x65, 0x82, // aTop saveCursor
4532 SIG_END
4533 };
4534
4535 static const uint16 kq6laurabow2CDPatchAudioTextSupport5[] = {
4536 0x18, 0x18, 0x18, 0x18, // (waste bytes)
4537 PATCH_END
4538 };
4539
4540 // Additional patch specifically for King's Quest 6
4541 // Fixes text window placement, when in "dual" mode
4542 // Applies to at least: PC-CD
4543 // Patched method: Kq6Talker::init
4544 static const uint16 kq6CDSignatureAudioTextSupport1[] = {
4545 SIG_MAGICDWORD,
4546 0x89, 0x5a, // lsg global[5a]
4547 0x35, 0x02, // ldi 02
4548 0x1a, // eq?
4549 0x31, SIG_ADDTOOFFSET(+1), // bnt [jump-to-text-code]
4550 0x78, // push1
4551 SIG_END
4552 };
4553
4554 static const uint16 kq6CDPatchAudioTextSupport1[] = {
4555 PATCH_ADDTOOFFSET(+4),
4556 0x12, // and
4557 PATCH_END
4558 };
4559
4560 // Additional patch specifically for King's Quest 6
4561 // Fixes low-res portrait staying on screen for hi-res mode
4562 // Applies to at least: PC-CD
4563 // Patched method: Talker::startText
4564 // this method is called by Narrator::say and acc is 0 for text-only and 2 for dual mode (audio+text)
4565 static const uint16 kq6CDSignatureAudioTextSupport2[] = {
4566 SIG_MAGICDWORD,
4567 0x3f, 0x01, // link 01
4568 0x63, 0x8a, // pToa viewInPrint
4569 0x18, // not
4570 0x31, 0x06, // bnt [skip following code]
4571 0x38, SIG_UINT16(0x00e1), // pushi 00e1
4572 0x76, // push0
4573 0x54, 0x04, // self 04
4574 SIG_END
4575 };
4576
4577 static const uint16 kq6CDPatchAudioTextSupport2[] = {
4578 PATCH_ADDTOOFFSET(+2),
4579 0x67, 0x8a, // pTos viewInPrint
4580 0x14, // or
4581 0x2f, // bt [skip following code]
4582 PATCH_END
4583 };
4584
4585 // Additional patch specifically for King's Quest 6
4586 // Fixes special windows, used for example in...
4587 // The Pawn shop (room 280), when the man in a robe complains about no more
4588 // mints.
4589 // Room 300 at the cliffs (aka copy protection), when Alexander falls down
4590 // the cliffs (closes automatically, but too late).
4591 // Room 210, when Alexander gives the ring to the nightingale (these need a
4592 // mouse click).
4593 //
4594 // We have to change even more code because the game uses PODialog class for
4595 // text windows and myDialog class for audio. Both are saved to
4596 // KQ6Print::dialog.
4597 //
4598 // Changing KQ6Print::dialog is disabled for now, because it has side-effects
4599 // (breaking game over screens).
4600 //
4601 // Original comment:
4602 // Sadly PODialog is created during KQ6Print::addText, myDialog is set during
4603 // KQ6Print::showSelf, which is called much later and KQ6Print::addText
4604 // requires KQ6Print::dialog to be set, which means we have to set it before
4605 // calling addText for audio mode, otherwise the user would have to click to
4606 // get those windows disposed.
4607 //
4608 // Applies to at least: PC-CD
4609 // Patched method: KQ6Print::say
4610 static const uint16 kq6CDSignatureAudioTextSupport3[] = {
4611 0x31, 0x6e, // bnt [to text code]
4612 SIG_ADDTOOFFSET(+85),
4613 SIG_MAGICDWORD,
4614 0x8f, 0x01, // lsp param[1]
4615 0x35, 0x01, // ldi 01
4616 0x1a, // eq?
4617 0x31, 0x0c, // bnt [code to set property repressText to 1]
4618 0x38, // pushi (selector addText)
4619 SIG_ADDTOOFFSET(+9), // skip addText-calling code
4620 0x33, 0x10, // jmp [to ret]
4621 0x35, 0x01, // ldi 01
4622 0x65, 0x2e, // aTop repressText
4623 0x33, 0x0a, // jmp [to ret]
4624 SIG_END
4625 };
4626
4627 static const uint16 kq6CDPatchAudioTextSupport3[] = {
4628 0x31, 0x68, // bnt (adjust branch to reuse audio mode addText-calling code)
4629 PATCH_ADDTOOFFSET(+85), // (right at the MAGIC_DWORD)
4630 // check, if text is supposed to be shown. If yes, skip the follow-up check (param[1])
4631 0x89, 0x5a, // lsg global[5a]
4632 0x35, 0x01, // ldi 01
4633 0x12, // and
4634 0x2f, 0x07, // bt [skip over param check]
4635 // original code, checks param[1]
4636 0x8f, 0x01, // lsp param[1]
4637 0x35, 0x01, // ldi 01
4638 0x1a, // eq?
4639 0x31, 0x10, // bnt [code to set property repressText to 1], adjusted
4640 // waste 5 bytes instead of using myDialog class for now
4641 // setting myDialog class all the time causes game over screens to misbehave (bug #9771)
4642 0x34, PATCH_UINT16(0x0000), // ldi 0 (waste 3 bytes)
4643 0x35, 0x00, // ldi 0 (waste 2 bytes)
4644 // use myDialog class, so that text box automatically disappears (this is not done for text only mode, like in the original)
4645 //0x72, 0x0e, 0x00, // lofsa myDialog
4646 //0x65, 0x12, // aTop dialog
4647 // followed by original addText-calling code
4648 0x38,
4649 PATCH_GETORIGINALUINT16(+95), // pushi (addText)
4650 0x78, // push1
4651 0x8f, 0x02, // lsp param[2]
4652 0x59, 0x03, // &rest 03
4653 0x54, 0x06, // self 06
4654 0x48, // ret
4655
4656 0x35, 0x01, // ldi 01
4657 0x65, 0x2e, // aTop repressText
4658 0x48, // ret
4659 PATCH_END
4660 };
4661
4662 // Additional patch specifically for King's Quest 6
4663 // Fixes text-window size for hires portraits mode
4664 // Otherwise at least at the end some text-windows will be way too small
4665 // Applies to at least: PC-CD
4666 // Patched method: Talker::init
4667 static const uint16 kq6CDSignatureAudioTextSupport4[] = {
4668 SIG_MAGICDWORD,
4669 0x63, 0x94, // pToa raving
4670 0x31, 0x0a, // bnt [no rave code]
4671 0x35, 0x00, // ldi 00
4672 SIG_ADDTOOFFSET(+6), // skip reset of bust, eyes and mouth
4673 0x33, 0x24, // jmp [to super class code]
4674 SIG_END
4675 };
4676
4677 static const uint16 kq6CDPatchAudioTextSupport4[] = {
4678 PATCH_ADDTOOFFSET(+12),
4679 0x33, PATCH_GETORIGINALBYTEADJUST(+13, -6), // (adjust jump to also include setSize call)
4680 PATCH_END
4681 };
4682
4683 // Fixes text window placement, when dual mode is active (Guards in room 220)
4684 // Applies to at least: PC-CD
4685 // Patched method: tlkGateGuard1::init & tlkGateGuard2::init
4686 static const uint16 kq6CDSignatureAudioTextSupportGuards[] = {
4687 SIG_MAGICDWORD,
4688 0x89, 0x5a, // lsg global[5a]
4689 0x35, 0x01, // ldi 01
4690 0x1a, // eq?
4691 SIG_END // followed by bnt for Guard1 and bt for Guard2
4692 };
4693
4694 static const uint16 kq6CDPatchAudioTextSupportGuards[] = {
4695 PATCH_ADDTOOFFSET(+2),
4696 0x35, 0x02, // ldi 02
4697 0x1c, // ne?
4698 PATCH_END
4699 };
4700
4701 // Fixes text window placement, when portrait+text is shown (Stepmother in room 250)
4702 // Applies to at least: PC-CD
4703 // Patched method: tlkStepmother::init
4704 static const uint16 kq6CDSignatureAudioTextSupportStepmother[] = {
4705 SIG_MAGICDWORD,
4706 0x89, 0x5a, // lsg global[5a]
4707 0x35, 0x02, // ldi 02
4708 0x12, // and
4709 0x31, // bnt [jump-for-text-code]
4710 SIG_END
4711 };
4712
4713 static const uint16 kq6CDPatchAudioTextSupportJumpAlways[] = {
4714 PATCH_ADDTOOFFSET(+4),
4715 0x1a, // eq?
4716 PATCH_END
4717 };
4718
4719 // Fixes "Girl In The Tower" to get played in dual mode as well
4720 // Also changes credits to use CD audio for dual mode.
4721 //
4722 // Applies to at least: PC-CD
4723 // Patched method: rm740::cue (script 740), sCredits::init (script 52)
4724 static const uint16 kq6CDSignatureAudioTextSupportGirlInTheTower[] = {
4725 SIG_MAGICDWORD,
4726 0x89, 0x5a, // lsg global[5a]
4727 0x35, 0x02, // ldi 02
4728 0x1a, // eq?
4729 0x31, // bnt [jump-for-text-code]
4730 SIG_END
4731 };
4732
4733 static const uint16 kq6CDPatchAudioTextSupportGirlInTheTower[] = {
4734 PATCH_ADDTOOFFSET(+4),
4735 0x12, // and
4736 PATCH_END
4737 };
4738
4739 // Fixes dual mode for scenes with Azure and Ariel (room 370)
4740 // Effectively same patch as the one for fixing "Girl In The Tower"
4741 // Applies to at least: PC-CD
4742 // Patched methods: rm370::init, caughtAtGateCD::changeState, caughtAtGateTXT::changeState, toLabyrinth::changeState
4743 // Fixes bug: #6750
4744 static const uint16 kq6CDSignatureAudioTextSupportAzureAriel[] = {
4745 SIG_MAGICDWORD,
4746 0x89, 0x5a, // lsg global[5a]
4747 0x35, 0x02, // ldi 02
4748 0x1a, // eq?
4749 0x31, // bnt [jump-for-text-code]
4750 SIG_END
4751 };
4752
4753 static const uint16 kq6CDPatchAudioTextSupportAzureAriel[] = {
4754 PATCH_ADDTOOFFSET(+4),
4755 0x12, // and
4756 PATCH_END
4757 };
4758
4759 // Additional patch specifically for King's Quest 6
4760 // Adds another button state for the text/audio button. We currently use the "speech" view for "dual" mode.
4761 // View 947, loop 9, cel 0+1 -> "text"
4762 // View 947, loop 8, cel 0+1 -> "speech"
4763 // View 947, loop 12, cel 0+1 -> "dual" (this view is injected by us into the game)
4764 // Applies to at least: PC-CD
4765 // Patched method: iconTextSwitch::show, iconTextSwitch::doit
4766 static const uint16 kq6CDSignatureAudioTextMenuSupport[] = {
4767 SIG_MAGICDWORD,
4768 0x89, 0x5a, // lsg global[5a]
4769 0x35, 0x02, // ldi 02
4770 0x1a, // eq?
4771 0x31, 0x06, // bnt [set text view]
4772 0x35, 0x08, // ldi 08
4773 0x65, 0x14, // aTop loop
4774 0x33, 0x04, // jmp [skip over text view]
4775 0x35, 0x09, // ldi 09
4776 0x65, 0x14, // aTop loop
4777 SIG_ADDTOOFFSET(+102), // skip to iconTextSwitch::doit code
4778 0x89, 0x5a, // lsg global[5a]
4779 0x3c, // dup
4780 0x35, 0x01, // ldi 01
4781 0x1a, // eq?
4782 0x31, 0x06, // bnt [set text mode]
4783 0x35, 0x02, // ldi 02
4784 0xa1, 0x5a, // sag global[5a]
4785 0x33, 0x0a, // jmp [skip over text mode code]
4786 0x3c, // dup
4787 0x35, 0x02, // ldi 02
4788 0x1a, // eq?
4789 0x31, 0x04, // bnt [skip over text ode code]
4790 0x35, 0x01, // ldi 01
4791 0xa1, 0x5a, // sag global[5a]
4792 0x3a, // toss
4793 0x67, 0x14, // pTos loop
4794 0x35, 0x09, // ldi 09
4795 0x1a, // eq?
4796 0x31, 0x04, // bnt [set text view]
4797 0x35, 0x08, // ldi 08
4798 0x33, 0x02, // jmp [skip text view]
4799 0x35, 0x09, // ldi 09
4800 0x65, 0x14, // aTop loop
4801 SIG_END
4802 };
4803
4804 static const uint16 kq6CDPatchAudioTextMenuSupport[] = {
4805 PATCH_ADDTOOFFSET(+13),
4806 0x33, 0x79, // jmp [to new text+dual code]
4807 PATCH_ADDTOOFFSET(+104), // seek to iconTextSwitch::doit
4808 0x81, 0x5a, // lag global[5a]
4809 0x78, // push1
4810 0x02, // add
4811 0xa1, 0x5a, // sag global[5a]
4812 0x36, // push
4813 0x35, 0x03, // ldi 03
4814 0x1e, // gt?
4815 0x31, 0x03, // bnt [skip over]
4816 0x78, // push1
4817 0xa9, 0x5a, // ssg global[5a]
4818 0x33, 0x17, // jmp [iconTextSwitch::show call]
4819 // additional code for iconTextSwitch::show
4820 0x89, 0x5a, // lsg global[5a]
4821 0x35, 0x01, // ldi 01
4822 0x1a, // eq?
4823 0x31, 0x04, // bnt [dual mode]
4824 0x35, 0x09, // ldi 09
4825 0x33, 0x02, // jmp [skip over dual mode]
4826 0x35, 0x0c, // ldi 0c (view 947, loop 12, cel 0+1 is our "dual" view, injected by view.cpp)
4827 0x65, 0x14, // aTop loop
4828 0x32, PATCH_UINT16(0xff75), // jmp [back to iconTextSwitch::show]
4829 PATCH_END
4830 };
4831
4832 // script, description, signature patch
4833 static const SciScriptPatcherEntry kq6Signatures[] = {
4834 { true, 87, "fix Drink Me bottle", 1, kq6SignatureDrinkMeFix, kq6PatchDrinkMeFix },
4835 { false, 87, "Mac: Drink Me pic", 1, kq6SignatureMacDrinkMePic, kq6PatchMacDrinkMePic },
4836 { true, 480, "CD: fix wallflower dance", 1, kq6CDSignatureWallFlowerDanceFix, kq6CDPatchWallFlowerDanceFix },
4837 { true, 481, "fix duplicate baby cry", 1, kq6SignatureDuplicateBabyCry, kq6PatchDuplicateBabyCry },
4838 { true, 640, "fix 'Tickets, only' message", 1, kq6SignatureTicketsOnly, kq6PatchTicketsOnly },
4839 { true, 907, "fix inventory stack leak", 1, kq6SignatureInventoryStackFix, kq6PatchInventoryStackFix },
4840 { true, 907, "fix hair detection for ribbon's look msg", 1, kq6SignatureLookRibbonFix, kq6PatchLookRibbonFix },
4841 // King's Quest 6 and Laura Bow 2 share basic patches for audio + text support
4842 // *** King's Quest 6 audio + text support ***
4843 { false, 924, "CD: audio + text support KQ6&LB2 1", 1, kq6laurabow2CDSignatureAudioTextSupport1, kq6laurabow2CDPatchAudioTextSupport1 },
4844 { false, 924, "CD: audio + text support KQ6&LB2 2", 1, kq6laurabow2CDSignatureAudioTextSupport2, kq6laurabow2CDPatchAudioTextSupport2 },
4845 { false, 924, "CD: audio + text support KQ6&LB2 3", 1, kq6laurabow2CDSignatureAudioTextSupport3, kq6laurabow2CDPatchAudioTextSupport3 },
4846 { false, 928, "CD: audio + text support KQ6&LB2 4", 1, kq6laurabow2CDSignatureAudioTextSupport4, kq6laurabow2CDPatchAudioTextSupport4 },
4847 { false, 928, "CD: audio + text support KQ6&LB2 5", 2, kq6laurabow2CDSignatureAudioTextSupport5, kq6laurabow2CDPatchAudioTextSupport5 },
4848 { false, 909, "CD: audio + text support KQ6 1", 2, kq6CDSignatureAudioTextSupport1, kq6CDPatchAudioTextSupport1 },
4849 { false, 928, "CD: audio + text support KQ6 2", 1, kq6CDSignatureAudioTextSupport2, kq6CDPatchAudioTextSupport2 },
4850 { false, 104, "CD: audio + text support KQ6 3", 1, kq6CDSignatureAudioTextSupport3, kq6CDPatchAudioTextSupport3 },
4851 { false, 928, "CD: audio + text support KQ6 4", 1, kq6CDSignatureAudioTextSupport4, kq6CDPatchAudioTextSupport4 },
4852 { false, 1009, "CD: audio + text support KQ6 Guards", 2, kq6CDSignatureAudioTextSupportGuards, kq6CDPatchAudioTextSupportGuards },
4853 { false, 1027, "CD: audio + text support KQ6 Stepmother", 1, kq6CDSignatureAudioTextSupportStepmother, kq6CDPatchAudioTextSupportJumpAlways },
4854 { false, 52, "CD: audio + text support KQ6 Girl In The Tower", 1, kq6CDSignatureAudioTextSupportGirlInTheTower, kq6CDPatchAudioTextSupportGirlInTheTower },
4855 { false, 740, "CD: audio + text support KQ6 Girl In The Tower", 1, kq6CDSignatureAudioTextSupportGirlInTheTower, kq6CDPatchAudioTextSupportGirlInTheTower },
4856 { false, 370, "CD: audio + text support KQ6 Azure & Ariel", 6, kq6CDSignatureAudioTextSupportAzureAriel, kq6CDPatchAudioTextSupportAzureAriel },
4857 { false, 903, "CD: audio + text support KQ6 menu", 1, kq6CDSignatureAudioTextMenuSupport, kq6CDPatchAudioTextMenuSupport },
4858 SCI_SIGNATUREENTRY_TERMINATOR
4859 };
4860
4861 #ifdef ENABLE_SCI32
4862 #pragma mark -
4863 #pragma mark Kings Quest 7
4864
4865 // KQ7's subtitles were left unfinished in the shipped game, so there are
4866 // several problems when enabling them from the ScummVM launcher:
4867 //
4868 // 1. `kqMessager::findTalker` tries to determine which class to use for
4869 // displaying subtitles using the talker number of each message, but the
4870 // talker data is often bogus (e.g. princess messages normally use talker 7,
4871 // but talker 99 (which is for the narrator) is used for her messages at the
4872 // start of chapter 2), so it can't be used.
4873 // 2. Some display classes render subtitles at the top or middle of the game
4874 // area, blocking the view of what is on the screen.
4875 // 3. In some areas, the colors of the subtitles are changed arbitrarily (e.g.
4876 // pink/purple at the start of chapter 2).
4877 //
4878 // To work around these problems, we always use KQTalker, force the text area to
4879 // the bottom of the game area, and force it to always use black & white, which
4880 // are guaranteed to not be changed by game scripts.
4881 //
4882 // We make 2 changes to KQNarrator::init and one to Narrator::say.
4883 //
4884 // Applies to at least: PC CD 1.4 English, 1.51 English, 1.51 German, 2.00 English
4885 static const uint16 kq7SubtitleFixSignature1[] = {
4886 SIG_MAGICDWORD,
4887 0x39, SIG_SELECTOR8(fore), // pushi fore ($25)
4888 0x78, // push1
4889 0x39, 0x06, // pushi 6 - sets fore to 6
4890 0x39, SIG_SELECTOR8(back), // pushi back ($26)
4891 0x78, // push1
4892 0x78, // push1 - sets back to 1
4893 0x39, SIG_SELECTOR8(font), // pushi font ($2a)
4894 0x78, // push1
4895 0x89, 0x16, // lsg global[$16] - sets font to global[$16]
4896 0x7a, // push2 (y)
4897 0x78, // push1
4898 0x76, // push0 - sets y to 0
4899 0x54, SIG_UINT16(0x0018), // self $18
4900 SIG_END
4901 };
4902
4903 static const uint16 kq7SubtitleFixPatch1[] = {
4904 0x33, 0x12, // jmp [skip special init code]
4905 PATCH_END
4906 };
4907
4908 // Applies to at least: PC CD 1.51 English, 1.51 German, 2.00 English
4909 static const uint16 kq7SubtitleFixSignature2[] = {
4910 SIG_MAGICDWORD,
4911 0x89, 0x5a, // lsg global[$5a]
4912 0x35, 0x02, // ldi 2
4913 0x12, // and
4914 0x31, 0x1e, // bnt [skip audio volume code]
4915 0x38, SIG_SELECTOR16(masterVolume), // pushi masterVolume (0212h for 2.00, 0219h for 1.51)
4916 0x76, // push0
4917 0x81, 0x01, // lag global[1]
4918 0x4a, SIG_UINT16(0x0004), // send 4
4919 0x65, 0x32, // aTop curVolume
4920 0x38, SIG_SELECTOR16(masterVolume), // pushi masterVolume (0212h for 2.00, 0219h for 1.51)
4921 0x78, // push1
4922 0x67, 0x32, // pTos curVolume
4923 0x35, 0x02, // ldi 2
4924 0x06, // mul
4925 0x36, // push
4926 0x35, 0x03, // ldi 3
4927 0x08, // div
4928 0x36, // push
4929 0x81, 0x01, // lag global[1]
4930 0x4a, SIG_UINT16(0x0006), // send 6
4931 // end of volume code
4932 0x35, 0x01, // ldi 1
4933 0x65, 0x28, // aTop initialized
4934 SIG_END
4935 };
4936
4937 static const uint16 kq7SubtitleFixPatch2[] = {
4938 PATCH_ADDTOOFFSET(+5), // skip to bnt
4939 0x31, 0x1b, // bnt [skip audio volume code]
4940 PATCH_ADDTOOFFSET(+15), // right after "aTop curVolume / pushi masterVolume / push1"
4941 0x7a, // push2
4942 0x06, // mul (saves 3 bytes in total)
4943 0x36, // push
4944 0x35, 0x03, // ldi 3
4945 0x08, // div
4946 0x36, // push
4947 0x81, 0x01, // lag global[1]
4948 0x4a, PATCH_UINT16(0x06), // send 6
4949 // end of volume code
4950 0x35, 0x76, // ldi 118
4951 0x65, 0x16, // aTop y
4952 0x78, // push1 (saves 1 byte)
4953 0x69, 0x28, // sTop initialized
4954 PATCH_END
4955 };
4956
4957 // Applies to at least: PC CD 1.51 English, 1.51 German, 2.00 English
4958 static const uint16 kq7SubtitleFixSignature3[] = {
4959 SIG_MAGICDWORD,
4960 0x63, 0x28, // pToa initialized
4961 0x18, // not
4962 0x31, 0x07, // bnt [skip init code]
4963 0x38, SIG_SELECTOR16(init), // pushi init ($8e for 2.00, $93 for 1.51)
4964 0x76, // push0
4965 0x54, SIG_UINT16(0x0004), // self 4
4966 // end of init code
4967 0x8f, 0x00, // lsp param[0]
4968 0x35, 0x01, // ldi 1
4969 0x1e, // gt?
4970 0x31, 0x08, // bnt [set acc to 0]
4971 0x87, 0x02, // lap param[2]
4972 0x31, 0x04, // bnt [set acc to 0]
4973 0x87, 0x02, // lap param[2]
4974 0x33, 0x02, // jmp [over set acc to 0 code]
4975 0x35, 0x00, // ldi 00
4976 0x65, 0x18, // aTop caller
4977 SIG_END
4978 };
4979
4980 static const uint16 kq7SubtitleFixPatch3[] = {
4981 PATCH_ADDTOOFFSET(+2), // skip over "pToa initialized code"
4982 0x2f, 0x0c, // bt [skip init code] - saved 1 byte
4983 0x38, PATCH_GETORIGINALUINT16(+6), // pushi init
4984 0x76, // push0
4985 0x54, PATCH_UINT16(0x0004), // self 4
4986 // additionally set background color here (5 bytes)
4987 0x34, PATCH_UINT16(0x00ff), // ldi 255
4988 0x65, 0x2e, // aTop back
4989 // end of init code
4990 0x8f, 0x00, // lsp param[0]
4991 0x35, 0x01, // ldi 1 (this may get optimized to get another byte)
4992 0x1e, // gt?
4993 0x31, 0x04, // bnt [set acc to 0]
4994 0x87, 0x02, // lap param[2]
4995 0x2f, 0x02, // bt [over set acc to 0 code]
4996 PATCH_END
4997 };
4998
4999 // KQ7 has custom video benchmarking code that needs to be disabled in a subroutine
5000 // that is called by KQ7CD::init; see sci2BenchmarkSignature
5001 static const uint16 kq7BenchmarkSignature[] = {
5002 0x38, SIG_SELECTOR16(new), // pushi new
5003 0x76, // push0
5004 0x51, SIG_ADDTOOFFSET(+1), // class Actor
5005 0x4a, SIG_UINT16(0x0004), // send 4
5006 0xa5, 0x00, // sat temp[0]
5007 0x39, SIG_SELECTOR8(view), // pushi view ($e)
5008 SIG_MAGICDWORD,
5009 0x78, // push1
5010 0x38, SIG_UINT16(0xfdd4), // pushi 64980
5011 SIG_END
5012 };
5013
5014 static const uint16 kq7BenchmarkPatch[] = {
5015 0x34, PATCH_UINT16(10000), // ldi 10000
5016 0x48, // ret
5017 PATCH_END
5018 };
5019
5020 // When attempting to use an inventory item on an object that does not interact
5021 // with that item, the game briefly displays an X cursor. It does this by
5022 // spinning for 90000 cycles, which makes the duration dependent on CPU speed,
5023 // maxes out the CPU for no reason, and keeps the engine from polling for
5024 // events (which may make the window appear nonresponsive to the OS).
5025 //
5026 // We replace the loop with a call to kWait().
5027 //
5028 // Applies to at least: KQ7 English 2.00b
5029 // Responsible method: KQ7CD::pragmaFail in script 0
5030 static const uint16 kq7PragmaFailSpinSignature[] = {
5031 0x35, 0x00, // ldi 0
5032 0xa5, 0x02, // sat temp[2]
5033 SIG_MAGICDWORD,
5034 0x8d, 0x02, // lst temp[2]
5035 0x35, 0x03, // ldi 3
5036 0x22, // lt?
5037 SIG_END
5038 };
5039
5040 static const uint16 kq7PragmaFailSpinPatch[] = {
5041 0x78, // push1
5042 0x39, 0x12, // pushi 18 (~300ms)
5043 0x43, kScummVMWaitId, PATCH_UINT16(0x02), // callk Wait, 2
5044 0x33, 0x16, // jmp [to setCursor]
5045 PATCH_END
5046 };
5047
5048 // Room 6100 creates an extra ambrosia, usually floating in upper left of the
5049 // screen, due to an off by one error. The script's local arrays contain four
5050 // ambrosia coordinates but the loop that accesses them iterates five times.
5051 //
5052 // Applies to: All versions
5053 // Responsible method: rm6100:init
5054 // Fixes bug #9790
5055 static const uint16 kq7ExtraAmbrosiaSignature[] = {
5056 SIG_MAGICDWORD,
5057 0x8d, 0x00, // lst 00
5058 0x35, 0x04, // ldi 04
5059 0x24, // le?
5060 SIG_END
5061 };
5062
5063 static const uint16 kq7ExtraAmbrosiaPatch[] = {
5064 PATCH_ADDTOOFFSET(+4),
5065 0x22, // lt?
5066 PATCH_END
5067 };
5068
5069 // In KQ7 1.4, after giving the statue to the snake oil salesman, the curtain is
5070 // drawn on top of ego when walking in front of the wagon. The script doesn't
5071 // dispose of the salesman and this leaves his final cel stuck on the screen.
5072 // We add the missing call to snakeSalesman:dispose.
5073 //
5074 // Applies to: English PC 1.4
5075 // Responsible method: giveStatue:changeState
5076 // Fixes bug: #10221
5077 static const uint16 kq7SnakeOilSalesmanSignature[] = {
5078 0x38, SIG_SELECTOR16(setHeading), // pushi setHeading
5079 SIG_ADDTOOFFSET(+0x281),
5080 0x72, SIG_UINT16(0x15b4), // lofsa snakeSalesman
5081 SIG_ADDTOOFFSET(+0x3f),
5082 0x3c, // dup
5083 0x35, SIG_MAGICDWORD, 0x0c, // ldi 0c
5084 0x1a, // eq?
5085 0x30, SIG_UINT16(0x0010), // bnt 0010 [ state 13 ]
5086 0x38, SIG_SELECTOR16(setHeading), // pushi setHeading
5087 0x7a, // pushi2
5088 0x38, SIG_UINT16(0x00b4), // pushi 00b4
5089 0x7c, // pushSelf
5090 0x81, 0x00, // lag 00
5091 0x4a, SIG_UINT16(0x0008), // send 08 [ KQEgo setHeading: 180 self ]
5092 0x32, SIG_UINT16(0x0017), // jmp 0017 [ end of method ]
5093 SIG_END
5094 };
5095
5096 static const uint16 kq7SnakeOilSalesmanPatch[] = {
5097 PATCH_ADDTOOFFSET(+0x02cd),
5098 0x38, PATCH_SELECTOR16(dispose), // pushi dispose
5099 0x76, // push0
5100 0x72, PATCH_UINT16(0x15b4), // lofsa snakeSalesman
5101 0x4a, PATCH_UINT16(0x0004), // send 04 [ snakeSalesman: dispose ]
5102 0x32, PATCH_UINT16(0xfd26), // jmp fd26 [ KQEgo setHeading and end of method ]
5103 PATCH_END
5104 };
5105
5106 // script, description, signature patch
5107 static const SciScriptPatcherEntry kq7Signatures[] = {
5108 { true, 0, "disable video benchmarking", 1, kq7BenchmarkSignature, kq7BenchmarkPatch },
5109 { true, 0, "remove hardcoded spin loop", 1, kq7PragmaFailSpinSignature, kq7PragmaFailSpinPatch },
5110 { true, 5300, "fix snake oil salesman disposal", 1, kq7SnakeOilSalesmanSignature, kq7SnakeOilSalesmanPatch },
5111 { true, 6100, "fix extra ambrosia", 1, kq7ExtraAmbrosiaSignature, kq7ExtraAmbrosiaPatch },
5112 { true, 31, "enable subtitles (1/3)", 1, kq7SubtitleFixSignature1, kq7SubtitleFixPatch1 },
5113 { true, 64928, "enable subtitles (2/3)", 1, kq7SubtitleFixSignature2, kq7SubtitleFixPatch2 },
5114 { true, 64928, "enable subtitles (3/3)", 1, kq7SubtitleFixSignature3, kq7SubtitleFixPatch3 },
5115 SCI_SIGNATUREENTRY_TERMINATOR
5116 };
5117
5118 #pragma mark -
5119 #pragma mark Lighthouse
5120
5121 // When going to room 5 (sierra logo & menu room) from room 380 (the credits
5122 // room), the game tries to clear flags from 0 (global[116] bit 0) to 1423
5123 // (global[204] bit 15), but global[201] is not a flag global (it holds a
5124 // reference to theInvisCursor). This patch stops clearing after flag 1359
5125 // (global[200] bit 15). Hopefully that is good enough to not break the game.
5126 // Applies to at least: English 1.0c & 2.0a
5127 static const uint16 lighthouseFlagResetSignature[] = {
5128 SIG_MAGICDWORD,
5129 0x34, SIG_UINT16(0x58f), // ldi 1423
5130 0x24, // le?
5131 SIG_END
5132 };
5133
5134 static const uint16 lighthouseFlagResetPatch[] = {
5135 0x34, PATCH_UINT16(0x54f), // ldi 1359
5136 PATCH_END
5137 };
5138
5139 // When doing a system check on the portal computer in the lighthouse, the game
5140 // counts up to 1024MB, one megabyte at a time. In SSCI, this count speed would
5141 // be video speed dependent, but with our frame rate throttler, it takes 17
5142 // seconds. So, replace this slowness with a much faster POST that is more
5143 // accurate to the original game.
5144 // Applies to at least: US English 1.0c
5145 static const uint16 lighthouseMemoryCountSignature[] = {
5146 SIG_MAGICDWORD,
5147 0x8d, 0x02, // lst temp[2]
5148 0x35, 0x0a, // ldi 10
5149 0x24, // le?
5150 0x31, 0x3b, // bnt [to second digit overflow]
5151 SIG_ADDTOOFFSET(+4), // ldi, sat
5152 0x8d, 0x03, // lst temp[3]
5153 0x35, 0x0a, // ldi 10
5154 SIG_END
5155 };
5156
5157 static const uint16 lighthouseMemoryCountPatch[] = {
5158 PATCH_ADDTOOFFSET(+2), // lst temp[2]
5159 0x35, 0x02, // ldi 2
5160 PATCH_ADDTOOFFSET(+9), // le?, bnt, ldi, sat, lst
5161 0x35, 0x02, // ldi 2
5162 PATCH_END
5163 };
5164
5165 // script, description, signature patch
5166 static const SciScriptPatcherEntry lighthouseSignatures[] = {
5167 { true, 5, "fix bad globals clear after credits", 1, lighthouseFlagResetSignature, lighthouseFlagResetPatch },
5168 { true, 360, "fix slow computer memory counter", 1, lighthouseMemoryCountSignature, lighthouseMemoryCountPatch },
5169 { true, 64990, "increase number of save games (1/2)", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
5170 { true, 64990, "increase number of save games (2/2)", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
5171 { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
5172 SCI_SIGNATUREENTRY_TERMINATOR
5173 };
5174
5175 #endif
5176
5177 // ===========================================================================
5178 // When Robin hands out the scroll to Marion and then types his name using the
5179 // hand code, the German version's script contains a typo (likely a copy/paste
5180 // error), and the local procedure that shows each letter is called twice. The
5181 // The procedure expects a letter arg and returns no value, so the first call
5182 // takes its letter and feeds an undefined value to the second call. Thus the
5183 // kStrCat() within the procedure reads a random pointer and crashes.
5184 //
5185 // We patch all of the 5 doubled local calls (one for each letter typed from
5186 // "R", "O", "B", "I", "N") to be the same as the English version.
5187 // Applies to at least: German floppy
5188 // Responsible method: giveScroll::changeState(19,21,23,25,27) in script 210
5189 // Fixes bug: #5264
5190 static const uint16 longbowSignatureShowHandCode[] = {
5191 0x78, // push1 (1 call arg)
5192 //
5193 0x78, // push1 (1 call arg)
5194 0x72, SIG_ADDTOOFFSET(+2), // lofsa (letter that was typed)
5195 0x36, // push
5196 0x40, SIG_ADDTOOFFSET(+2), 0x02, // call [localproc], 02
5197 //
5198 0x36, // push (the result is an arg for the next call)
5199 0x40, SIG_ADDTOOFFSET(+2), SIG_MAGICDWORD, 0x02, // call [localproc], 02
5200 //
5201 0x38, SIG_SELECTOR16(setMotion), // pushi setMotion (0x11c in Longbow German)
5202 0x39, SIG_SELECTOR8(x), // pushi x (0x04 in Longbow German)
5203 0x51, 0x1e, // class MoveTo
5204 SIG_END
5205 };
5206
5207 static const uint16 longbowPatchShowHandCode[] = {
5208 0x39, 0x01, // pushi 1 (combine the two push1's in one, like in the English version)
5209 PATCH_ADDTOOFFSET(+3), // leave the lofsa untouched
5210 // The following will remove the first push & call
5211 0x32, PATCH_UINT16(0x0002), // jmp 02 [to the second push & call]
5212 0x35, 0x00, // ldi 0 (waste 2 bytes)
5213 PATCH_END
5214 };
5215
5216 // When walking through the forest, arithmetic errors may occur at "random".
5217 // The scripts try to add a value and a pointer to the object "berryBush".
5218 //
5219 // This is caused by a local variable overflow.
5220 //
5221 // The scripts create berry bush objects dynamically. The array storage for
5222 // those bushes may hold a total of 8 bushes. But sometimes 10 bushes
5223 // are created. This overwrites 2 additional locals in script 225 and
5224 // those locals are used normally for value lookups.
5225 //
5226 // Changing the total of bushes could cause all sorts of other issues,
5227 // that's why I rather patched the code, that uses the locals for a lookup.
5228 // Which means it doesn't matter anymore when those locals are overwritten.
5229 //
5230 // Applies to at least: English PC floppy, German PC floppy, English Amiga floppy
5231 // Responsible method: export 2 of script 225
5232 // Fixes bug: #6751
5233 static const uint16 longbowSignatureBerryBushFix[] = {
5234 0x89, 0x70, // lsg global[70h]
5235 0x35, 0x03, // ldi 03h
5236 0x1a, // eq?
5237 0x2e, SIG_UINT16(0x002d), // bt [process code]
5238 0x89, 0x70, // lsg global[70h]
5239 0x35, 0x04, // ldi 04h
5240 0x1a, // eq?
5241 0x2e, SIG_UINT16(0x0025), // bt [process code]
5242 0x89, 0x70, // lsg global[70h]
5243 0x35, 0x05, // ldi 05h
5244 0x1a, // eq?
5245 0x2e, SIG_UINT16(0x001d), // bt [process code]
5246 0x89, 0x70, // lsg global[70h]
5247 0x35, 0x06, // ldi 06h
5248 0x1a, // eq?
5249 0x2e, SIG_UINT16(0x0015), // bt [process code]
5250 0x89, 0x70, // lsg global[70h]
5251 0x35, 0x18, // ldi 18h
5252 0x1a, // eq?
5253 0x2e, SIG_UINT16(0x000d), // bt [process code]
5254 0x89, 0x70, // lsg global[70h]
5255 0x35, 0x19, // ldi 19h
5256 0x1a, // eq?
5257 0x2e, SIG_UINT16(0x0005), // bt [process code]
5258 0x89, 0x70, // lsg global[70h]
5259 0x35, 0x1a, // ldi 1Ah
5260 0x1a, // eq?
5261 // jump location for the "bt" instructions
5262 0x30, SIG_UINT16(0x0011), // bnt [skip over follow up code, to offset 0c35]
5263 // 55 bytes until here
5264 0x85, 0x00, // lat temp[0]
5265 SIG_MAGICDWORD,
5266 0x9a, SIG_UINT16(0x0110), // lsli local[110h] -> 110h points normally to 110h / 2Bh
5267 // 5 bytes
5268 0x7a, // push2
5269 SIG_END
5270 };
5271
5272 static const uint16 longbowPatchBerryBushFix[] = {
5273 PATCH_ADDTOOFFSET(+4), // keep: lsg global[70h], ldi 03h
5274 0x22, // lt? (global < 03h)
5275 0x2f, 0x42, // bt [skip over all the code directly]
5276 0x89, 0x70, // lsg global[70h]
5277 0x35, 0x06, // ldi 06h
5278 0x24, // le? (global <= 06h)
5279 0x2f, 0x0e, // bt [to kRandom code]
5280 0x89, 0x70, // lsg global[70h]
5281 0x35, 0x18, // ldi 18h
5282 0x22, // lt? (global < 18h)
5283 0x2f, 0x34, // bt [skip over all the code directly]
5284 0x89, 0x70, // lsg global[70h]
5285 0x35, 0x1a, // ldi 1Ah
5286 0x24, // le? (global <= 1Ah)
5287 0x31, 0x2d, // bnt [skip over all the code directly]
5288 // 28 bytes, 27 bytes saved
5289 // kRandom code
5290 0x85, 0x00, // lat temp[0]
5291 0x2f, 0x05, // bt [skip over case 0]
5292 // temp[0] == 0
5293 0x38, PATCH_UINT16(0x0110), // pushi 0110h - that's what's normally at local[110h]
5294 0x33, 0x18, // jmp [kRandom call]
5295 // check temp[0] further
5296 0x78, // push1
5297 0x1a, // eq?
5298 0x31, 0x05, // bnt [skip over case 1]
5299 // temp[0] == 1
5300 0x38, PATCH_UINT16(0x002b), // pushi 002Bh - that's what's normally at local[111h]
5301 0x33, 0x0f, // jmp [kRandom call]
5302 // temp[0] >= 2
5303 0x8d, 0x00, // lst temp[0]
5304 0x35, 0x02, // ldi 02
5305 0x04, // sub
5306 0x9a, PATCH_UINT16(0x0112), // lsli local[112h] -> look up value in 2nd table
5307 // this may not be needed at all and was just added for safety reasons
5308 // waste 9 spare bytes
5309 0x35, 0x00, // ldi 00
5310 0x35, 0x00, // ldi 00
5311 0x34, PATCH_UINT16(0x0000), // ldi 0000
5312 PATCH_END
5313 };
5314
5315 // The camp (room 150) has a bug that can prevent the outlaws from ever rescuing
5316 // the boys at sunset on day 5 or 6. The rescue occurs when entering camp as an
5317 // abbey monk after leaving town exactly 3 times but this assumes that the
5318 // counter can't exceed this. Wearing a different disguise can increment the
5319 // counter beyond 3 at which point sunset can never occur.
5320 //
5321 // We fix this by patching the counter tests to greater than or equals. This
5322 // makes them consistent with the other scripts that test this global variable.
5323 //
5324 // Applies to: English PC Floppy, German PC Floppy, English Amiga Floppy
5325 // Responsible method: local procedure #3 in script 150
5326 // Fixes bug: #10839
5327 static const uint16 longbowSignatureCampSunsetFix[] = {
5328 SIG_MAGICDWORD,
5329 0x89, 0x8e, // lsg global[8e] [ times left town ]
5330 0x35, 0x03, // ldi 03
5331 0x1a, // eq?
5332 SIG_END
5333 };
5334
5335 static const uint16 longbowPatchCampSunsetFix[] = {
5336 PATCH_ADDTOOFFSET(+4),
5337 0x20, // ge?
5338 PATCH_END
5339 };
5340
5341 // The town map (room 260) has a bug that can send Robin to the wrong room.
5342 // Loading the map from town on day 5 or 6 automatically sends Robin to camp
5343 // (room 150) after leaving town more than twice. The intent is to start the
5344 // sunset scene where the outlaws rescue the boys, but the map doesn't test the
5345 // correct sunset conditions and can load an empty camp, even on the wrong day.
5346 //
5347 // We fix this by changing the map's logic to match the camp's by requiring the
5348 // abbey monk disguise to be worn and the rescue flag to not be set.
5349 //
5350 // Applies to: English PC Floppy, German PC Floppy, English Amiga Floppy
5351 // Responsible method: rm260:init
5352 // Fixes bug: #10839
5353 static const uint16 longbowSignatureTownMapSunsetFix[] = {
5354 SIG_MAGICDWORD,
5355 0x39, 0x05, // pushi 05
5356 0x81, 0x82, // lag global[82] [ day ]
5357 0x24, // le?
5358 0x30, SIG_UINT16(0x0089), // bnt 0089 [ no sunset if day < 5 ]
5359 0x60, // pprev
5360 0x35, 0x06, // ldi 06
5361 0x24, // le?
5362 0x30, SIG_UINT16(0x0082), // bnt 0082 [ no sunset if day > 6 ]
5363 0x89, 0x8e, // lsg global[8e]
5364 0x35, 0x01, // ldi 01
5365 SIG_END
5366 };
5367
5368 static const uint16 longbowPatchTownMapSunsetFix[] = {
5369 0x89, 0x7e, // lsg global[7e] [ current disguise ]
5370 0x35, 0x05, // ldi 05 [ abbey monk ]
5371 0x1c, // ne?
5372 0x2f, 0x06, // bt 06 [ no sunset if disguise != abbey monk ]
5373 0x78, // push1
5374 0x39, 0x38, // pushi 38
5375 0x45, 0x05, 0x02, // callb [export 5 of script 0], 02 [ is rescue flag set? ]
5376 0x2e, PATCH_UINT16(0x0081), // bt 0081 [ no sunset if rescue flag is set ]
5377 0x81, 0x8e, // lag global[8e]
5378 0x78, // push1 [ save a byte ]
5379 PATCH_END
5380 };
5381
5382 // Ending day 5 or 6 by choosing to attack the castle fails to set the rescue
5383 // flag which tells the next day what to do. This flag is set when rescuing
5384 // the boys yourself and when the outlaws rescue them at sunset. Without this
5385 // flag, the sunset rescue can repeat the next day and break the game.
5386 //
5387 // We fix this by setting the flag when returning the boys to their mother in
5388 // room 250 after the attack.
5389 //
5390 // Applies to: English PC Floppy, German PC Floppy, English Amiga Floppy
5391 // Responsible method: boysSaved:changeState(0)
5392 // Fixes bug: #10839
5393 static const uint16 longbowSignatureRescueFlagFix[] = {
5394 0x3c, // dup
5395 0x35, 0x00, // ldi 00
5396 0x1a, // eq?
5397 0x30, SIG_MAGICDWORD, // bnt 0003 [ state 1 ]
5398 SIG_UINT16(0x0003),
5399 0x32, SIG_UINT16(0x025b), // jmp 025b [ end of method ]
5400 SIG_END
5401 };
5402
5403 static const uint16 longbowPatchRescueFlagFix[] = {
5404 0x2f, 0x08, // bt 08 [ state 1 ]
5405 0x78, // push1
5406 0x39, 0x38, // pushi 38
5407 0x45, 0x06, 0x02, // callb [export 6 of script 0], 02 [ set rescue flag ]
5408 0x3a, // toss
5409 0x48, // ret
5410 PATCH_END
5411 };
5412
5413 // On day 7, Tuck can appear at camp to say that the widow wants to see you when
5414 // she really doesn't. This scene is only supposed to occur if you haven't
5415 // received the net but the script only tests if the net is currently in
5416 // inventory, which it isn't if you've already used it or are in disguise.
5417 //
5418 // We fix this by testing the net's owner instead of inventory. If net:owner is
5419 // non-zero then it's in inventory or in your cave or has been used.
5420 //
5421 // Applies to: English PC Floppy, German PC Floppy, English Amiga Floppy
5422 // Responsible method: local procedure #3 in script 150
5423 // Fixes bug: #10847
5424 static const uint16 longbowSignatureTuckNetFix[] = {
5425 SIG_MAGICDWORD,
5426 0x30, SIG_UINT16(0x03a2), // bnt 03a2 [ end of method ]
5427 0x38, SIG_SELECTOR16(has), // pushi has
5428 0x78, // push1
5429 0x39, 0x04, // pushi 04
5430 0x81, 0x00, // lag global[0]
5431 0x4a, 0x06, // send 6 [ ego: has 4 ]
5432 0x18, // not
5433 0x30, SIG_UINT16(0x0394), // bnt 0394 [ end of method if net not in inventory ]
5434 0x78, // push1
5435 0x39, 0x47, // pushi 47
5436 0x45, 0x05, 0x02, // callb [export 5 of script 0], 02 [ is flag 47 set? ]
5437 0x18, // not
5438 0x30, SIG_UINT16(0x038a), // bnt 038a [ end of method ]
5439 SIG_ADDTOOFFSET(+60),
5440 0x32, SIG_UINT16(0x034b), // jmp 034b [ end of method ]
5441 SIG_END
5442 };
5443
5444 static const uint16 longbowPatchTuckNetFix[] = {
5445 0x31, 0x55, // bnt 55 [ skip scene, save a byte ]
5446 0x39, PATCH_SELECTOR8(at), // pushi at
5447 0x78, // push1
5448 0x39, 0x04, // pushi 04
5449 0x81, 0x09, // lag global[9]
5450 0x4a, 0x06, // send 6 [ Inv: at 4 ]
5451 0x38, PATCH_SELECTOR16(owner), // pushi owner
5452 0x76, // push0
5453 0x4a, 0x04, // send 4 [ net: owner? ]
5454 0x2f, 0x44, // bt 44 [ skip scene if net:owner != 0 ]
5455 0x78, // push1
5456 0x39, 0x47, // pushi 47
5457 0x45, 0x05, 0x02, // callb [export 5 of script 0], 02 [ is flag 47 set? ]
5458 0x2f, 0x3c, // bt 3c [ skip scene, save 2 bytes ]
5459 PATCH_END
5460 };
5461
5462 // On day 9, room 350 outside the cobbler's hut is initialized incorrectly if
5463 // disguised as a monk. The entrance to the hut is broken and several minor
5464 // messages are incorrect. This is due to the room's script assuming that the
5465 // only disguises that day are yeoman and merchant. A monk disguise causes some
5466 // tests to pass and others to fail, leaving the room in an inconsistent state.
5467 //
5468 // We fix this by changing the yeoman disguise tests in the script to include
5469 // the monk disguises. The disguise global is set to 4 for yeoman and 5 or 6
5470 // for monk disguises so we patch the tests to be greater than or equals to.
5471 //
5472 // Applies to: English PC Floppy, German PC Floppy, English Amiga Floppy
5473 // Responsible methods: rm350:init, lobbsHut:doVerb, lobbsDoor:doVerb,
5474 // lobbsCover:doVerb, tailorDoor:doVerb
5475 // Fixes bug: #10834
5476 static const uint16 longbowSignatureCobblerHut[] = {
5477 SIG_MAGICDWORD,
5478 0x89, 0x7e, // lsg global[7e] [ current disguise ]
5479 0x35, 0x04, // ldi 04 [ yeoman ]
5480 0x1a, // eq? [ is current disguise yeoman? ]
5481 SIG_END
5482 };
5483
5484 static const uint16 longbowPatchCobblerHut[] = {
5485 PATCH_ADDTOOFFSET(+4),
5486 0x20, // ge? [ is current disguise yeoman or monk? ]
5487 PATCH_END
5488 };
5489
5490 // The Amiga version of room 530 adds a broken fDrunk:onMe method which prevents
5491 // messages when clicking on the drunk on the floor of the pub and causes a
5492 // signature mismatch on every click in the room. fDrunk:onMe passes an Event
5493 // object as an integer x coordinate and an uninitialized parameter as a y
5494 // coordinate to kOnControl. This is a signature mismatch and would cause onMe
5495 // to return false on every click and prevent hit testing from dispatching
5496 // events to fDrunk. It's unclear why Sierra added this method to this one
5497 // Feature in the room. Even if it worked, Feature:onMe already does this.
5498 //
5499 // We fix this by replacing fDrunk:onMe's contents with a call to super:onMe
5500 // which calls kOnControl correctly and does proper hit testing, making its
5501 // behavior consistent with the DOS version, which doesn't override onMe.
5502 //
5503 // Applies to: English Amiga Floppy
5504 // Responsible method: fDrunk:onMe
5505 // Fixes bug: #9688
5506 static const uint16 longbowSignatureAmigaPubFix[] = {
5507 SIG_MAGICDWORD,
5508 0x67, 0x20, // pTos onMeCheck
5509 0x39, 0x03, // pushi 03
5510 0x39, 0x04, // pushi 04
5511 0x8f, 0x01, // lsp param[1]
5512 0x8f, 0x02, // lsp param[2]
5513 0x43, 0x4e, 0x06, // callk OnControl, 6
5514 SIG_END
5515 };
5516
5517 static const uint16 longbowPatchAmigaPubFix[] = {
5518 0x38, PATCH_UINT16(0x00c4), // pushi 00c4 [ onMe, hard-coded for amiga ]
5519 0x76, // push0
5520 0x59, 0x01, // &rest 1
5521 0x57, 0x2c, 0x04, // super Feature, 4 [ super: onMe &rest ]
5522 0x48, // ret
5523 PATCH_END
5524 };
5525
5526 // WORKAROUND: Script needed, because of differences in our pathfinding
5527 // algorithm
5528 // When the guards kick Robin out of archery room 320 the game locks up due to
5529 // pathfinding algorithm differences. Ours sends ego in the wrong direction,
5530 // colliding with a guard, and preventing the script from continuing.
5531 //
5532 // Applies to: English PC Floppy, German PC Floppy, English Amiga Floppy
5533 // Responsible method: takeHimOut:changeState(1)
5534 // Fixes bug: #10896
5535 static const uint16 longbowSignatureArcherPathfinding[] = {
5536 SIG_MAGICDWORD,
5537 0x38, SIG_UINT16(0x00c8), // pushi 00c8 [ y = 200 ]
5538 0x7c, // pushSelf
5539 0x81, 0x00, // lag 00
5540 0x4a, 0x0c, // send 0c [ ego setMotion: PolyPath (ego x?) 200 self ]
5541 SIG_END
5542 };
5543
5544 static const uint16 longbowPatchArcherPathfinding[] = {
5545 0x38, PATCH_UINT16(0x00c4), // pushi 00c4 [ y = 196 ]
5546 PATCH_END
5547 };
5548
5549 // Longbow 1.0 has two random but common game-breaking bugs: Green Man's riddle
5550 // scene never ends and the Sheriff's men catch Robin too quickly when sweeping
5551 // the forest. Both are due to reusing an uninitialized global variable.
5552 //
5553 // Global 137 is used by the abbey hedge maze to store ego's cel during room
5554 // transitions. Exiting the maze leaves this as a random value between 0 and 5.
5555 // The forest sweep also uses this global but as a counter it expects to start
5556 // at 0. It increments as Robin changes rooms during a sweep until it reaches a
5557 // a maximum and he is caught. This is usually 7 but in some rooms it's only 3.
5558 // A high initial value can make this sequence impossible. rm180:doit also
5559 // tests the sweep counter and doesn't allow scripts to respond to a hand code
5560 // when greater than 2. This breaks the riddle scene after the first answer.
5561 //
5562 // We fix this by clearing global 137 at the start of days 1-7 and 11 so that
5563 // stale hedge maze values from days 5/6 and 10 don't affect the day 7 riddles
5564 // or the sweeps on days 9 and 12. Ideally we could just clear this at the
5565 // start of each day but there's no day initialization script. Instead we add
5566 // our day-specific code to Robin's cave (room 140), similar to Sierra's patch
5567 // and later versions.
5568 //
5569 // Applies to: English PC Floppy 1.0
5570 // Responsible method: localproc_001a in script 140
5571 // Fixes bug #5036
5572 static const uint16 longbowSignatureGreenManForestSweepFix[] = {
5573 0x89, SIG_MAGICDWORD, 0x82, // lsg 82 [ day ]
5574 0x35, 0x01, // ldi 01
5575 0x1a, // eq?
5576 0x30, SIG_UINT16(0x0019), // bnt 0019 [ skip horn init ]
5577 0x38, SIG_SELECTOR16(has), // pushi has
5578 0x78, // push1
5579 0x78, // push1
5580 0x81, 0x00, // lag 00
5581 0x4a, 0x06, // send 06 [ ego has: 1 ]
5582 0x18, // not
5583 0x30, SIG_UINT16(0x000c), // bnt 000c [ skip horn init ]
5584 0x39, SIG_SELECTOR8(init), // pushi init
5585 0x76, // push0
5586 0x38, SIG_ADDTOOFFSET(+2), // pushi stopUpd
5587 0x76, // push0
5588 0x72, SIG_UINT16(0x19b2), // lofsa horn
5589 0x4a, 0x08, // send 08 [ horn init: stopUpd: ]
5590 0x89, 0x7e, // lsg 7e
5591 0x35, 0x00, // ldi 00
5592 0x1a, // eq?
5593 0x2e, SIG_UINT16(0005), // bt 0005
5594 SIG_ADDTOOFFSET(+19),
5595 0x39, SIG_SELECTOR8(init), // push init
5596 0x76, // push0
5597 0x38, SIG_ADDTOOFFSET(+2), // pushi stopUpd
5598 0x76, // push0
5599 0x72, SIG_UINT16(0x1912), // lofsa bow
5600 SIG_END
5601 };
5602
5603 static const uint16 longbowPatchGreenManForestSweepFix[] = {
5604 0x39, 0x07, // pushi 07
5605 0x81, 0x82, // lag 82 [ day ]
5606 0x22, // lt?
5607 0x31, 0x06, // bnt 06
5608 0x60, // pprev [ day ]
5609 0x35, 0x0b, // ldi 0b
5610 0x1c, // ne?
5611 0x2f, 0x02, // bt 02
5612 0xa1, 0x89, // sag 89 [ sweep-count = 0 if day <= 7 or day == 11 ]
5613 0x81, 0x82, // lag 82 [ day ]
5614 0x78, // push1
5615 0x1a, // eq?
5616 0x31, 0x10, // bnt 10 [ skip horn init ]
5617 0x38, PATCH_SELECTOR16(has), // pushi has
5618 0x78, // push1
5619 0x78, // push1
5620 0x81, 0x00, // lag 00
5621 0x4a, 0x06, // send 06 [ ego has: 1 ]
5622 0x2f, 0x05, // bt 05 [ skip horn init ]
5623 0x72, PATCH_UINT16(0x19b2), // lofsa horn
5624 0x33, 0x1a, // jmp 1c [ continue horn init ]
5625 0x81, 0x7e, // lag 7e
5626 0x31, 0x08, // bnt 08
5627 PATCH_ADDTOOFFSET(+19),
5628 0x72, PATCH_UINT16(0x1912), // lofsa bow
5629 0x39, PATCH_SELECTOR8(init), // push init
5630 0x76, // push0
5631 0x38, PATCH_GETORIGINALUINT16(+25), // pushi stopUpd
5632 0x76, // push0
5633 PATCH_END
5634 };
5635
5636 // After rescuing Fulk in the Amiga version, rescueOfFulk stores the boat speed
5637 // in a temporary variable during one state and expects it to still be there in
5638 // a later state, which only worked by accident in Sierra's interpreter. This
5639 // Amiga tweak was made so that on slower machines the boat would animate after
5640 // Fulk and Robin leave the screen. We fix this by using the script's register
5641 // property for storage instead of a temporary variable.
5642 //
5643 // Applies to: English Amiga Floppy
5644 // Responsible method: rescueOfFulk:changeState
5645 // Fixes bug: #11137
5646 static const uint16 longbowSignatureAmigaFulkRescue[] = {
5647 SIG_MAGICDWORD,
5648 0xa5, 0x00, // sat 00
5649 0x89, 0x57, // lsg 87
5650 SIG_ADDTOOFFSET(+10),
5651 0x8d, 0x00, // lst 00
5652 SIG_ADDTOOFFSET(+635),
5653 0x8d, 0x00, // lst 00
5654 SIG_END
5655 };
5656
5657 static const uint16 longbowPatchAmigaFulkRescue[] = {
5658 0x65, 0x1a, // aTop register
5659 PATCH_ADDTOOFFSET(+12),
5660 0x67, 0x1a, // pTos register
5661 PATCH_ADDTOOFFSET(+635),
5662 0x67, 0x1a, // pTos register
5663 PATCH_END
5664 };
5665
5666 // The Amiga version has an unusual speed test which takes 10 seconds to run in
5667 // ScummVM, causing the test to assume a slow machine speed and reduce details
5668 // throughout the game. We disable the speed test and its long delay before the
5669 // the Sierra logo so that the fastest machine speed is used.
5670 //
5671 // Applies to: English Amiga Floppy
5672 // Responsible method: speedScript:changeState
5673 static const uint16 longbowSignatureAmigaSpeedTest[] = {
5674 // state 1
5675 0x32, SIG_UINT16(0x0164), // jmp 0164 [ end of method ]
5676 SIG_ADDTOOFFSET(+0xe9),
5677 // state 2
5678 SIG_MAGICDWORD,
5679 0x35, 0x02, // ldi 02 [ fastest machine speed ]
5680 0x32, SIG_UINT16(0x000f), // jmp 000f [ set machine speed ]
5681 SIG_END
5682 };
5683
5684 static const uint16 longbowPatchAmigaSpeedTest[] = {
5685 0x32, PATCH_UINT16(0x00e9), // jmp 00e9 [ skip test, use fastest machine speed ]
5686 PATCH_END
5687 };
5688
5689 // script, description, signature patch
5690 static const SciScriptPatcherEntry longbowSignatures[] = {
5691 { true, 140, "green man riddles and forest sweep fix", 1, longbowSignatureGreenManForestSweepFix, longbowPatchGreenManForestSweepFix },
5692 { true, 150, "day 5/6 camp sunset fix", 2, longbowSignatureCampSunsetFix, longbowPatchCampSunsetFix },
5693 { true, 150, "day 7 tuck net fix", 1, longbowSignatureTuckNetFix, longbowPatchTuckNetFix },
5694 { true, 210, "hand code crash", 5, longbowSignatureShowHandCode, longbowPatchShowHandCode },
5695 { true, 225, "arithmetic berry bush fix", 1, longbowSignatureBerryBushFix, longbowPatchBerryBushFix },
5696 { true, 250, "day 5/6 rescue flag fix", 1, longbowSignatureRescueFlagFix, longbowPatchRescueFlagFix },
5697 { true, 260, "day 5/6 town map sunset fix", 1, longbowSignatureTownMapSunsetFix, longbowPatchTownMapSunsetFix },
5698 { true, 320, "day 8 archer pathfinding workaround", 1, longbowSignatureArcherPathfinding, longbowPatchArcherPathfinding },
5699 { true, 350, "day 9 cobbler hut fix", 10, longbowSignatureCobblerHut, longbowPatchCobblerHut },
5700 { true, 530, "amiga pub fix", 1, longbowSignatureAmigaPubFix, longbowPatchAmigaPubFix },
5701 { true, 600, "amiga fulk rescue fix", 1, longbowSignatureAmigaFulkRescue, longbowPatchAmigaFulkRescue },
5702 { true, 803, "amiga speed test", 1, longbowSignatureAmigaSpeedTest, longbowPatchAmigaSpeedTest },
5703 SCI_SIGNATUREENTRY_TERMINATOR
5704 };
5705
5706 // ===========================================================================
5707 // Leisure Suit Larry 1 (Spanish)
5708 //
5709 // It seems originally the Spanish version of Larry 1 used some beta code at
5710 // least for the man wearing a barrel, who walks around in front of the casino.
5711 // The script inside the resource files even uses a class, that does not exist
5712 // inside those resource files, which causes a hard error.
5713 // The patch files included with the Spanish version (300.scr,300.tex, 927.scr)
5714 // add this class, but at least inside ScummVM a write to a non-existent selector
5715 // happens right after the player tries to buy an apple from that man.
5716 //
5717 // In the original English releases (2.0+2.1) this was handled differently.
5718 // Which is why this script patch changes that code to work just like in the English release.
5719 //
5720 // Attention: for at least some release of this game, view 302 (man wearing a barrel) is fully
5721 // broken! Which also causes a crash. The original interpreter crashes as well.
5722 // The only way to fix this is to dump that view from another release of Larry 1
5723 // and then use the view patch file on this release.
5724 //
5725 // Applies to at least: Spanish floppy
5726 // Responsible method: sBuyApple::changeScript(2)
5727 // Fixes bug: #10240
5728 static const uint16 larry1SignatureBuyApple[] = {
5729 // end of state 0
5730 0x35, 0x01, // ldi 01
5731 0x65, 0x10, // aTop cycles
5732 0x32, SIG_UINT16(0x0248), // jmp [ret]
5733 0x3c, // dup
5734 0x35, 0x01, // ldi 01
5735 0x1a, // eq?
5736 0x30, SIG_UINT16(0x0007), // bnt [step 2 check]
5737 // state 1 code
5738 0x35, 0x01, // ldi 01
5739 0x65, 0x10, // aTop cycles
5740 0x32, SIG_UINT16(0x023a), // jmp [ret]
5741 0x3c, // dup
5742 0x35, 0x02, // ldi 02
5743 0x1a, // eq?
5744 0x30, SIG_UINT16(0x0036), // bnt [step 3 check]
5745 // state 2 code
5746 0x35, 0x02, // ldi 02
5747 0x38, SIG_UINT16(0x0091), // pushi setCycle
5748 0x78, // push1
5749 0x51, 0x18, // class Walk
5750 0x36, // push
5751 0x38, SIG_UINT16(0x0126), // pushi setAvoider
5752 0x78, // push1
5753 0x51, SIG_ADDTOOFFSET(+1), // class PAvoider (original 0x25, w/ patch file 0x6d)
5754 0x36, // push
5755 0x38, SIG_UINT16(0x0116), // pushi setMotion
5756 SIG_MAGICDWORD,
5757 0x39, 0x04, // pushi 04
5758 0x51, 0x24, // class PolyPath
5759 0x36, // push
5760 0x39, 0x04, // pushi 04
5761 0x76, // push0
5762 0x72, SIG_UINT16(0x0f4e), // lofsa aAppleMan
5763 0x4a, 0x04, // send 04
5764 0x36, // push
5765 0x35, 0x1d, // ldi 1Dh
5766 0x02, // add
5767 0x36, // push
5768 0x39, 0x03, // pushi 03
5769 0x76, // push0
5770 0x72, SIG_UINT16(0x0f4e), // lofsa aAppleMan
5771 0x4a, 0x04, // send 04
5772 0x36, // push
5773 0x7c, // pushSelf
5774 0x81, 0x00, // lag global[0]
5775 0x4a, 0x18, // send 18h
5776 0x32, SIG_UINT16(0x01fd), // jmp [ret]
5777 SIG_END
5778 };
5779
5780 static const uint16 larry1PatchBuyApple[] = {
5781 PATCH_ADDTOOFFSET(+11),
5782 0x2f, 0xf3, // bt [jump to end of step 1 code], saves 8 bytes
5783 0x3c, // dup
5784 0x35, 0x02, // ldi 02
5785 0x1a, // eq?
5786 0x31, 0x3f, // bnt [step 3 check]
5787 0x38, PATCH_UINT16(0x00e1), // pushi distanceTo
5788 0x78, // push1
5789 0x72, PATCH_UINT16(0x0f4e), // lofsa sAppleMan
5790 0x36, // push
5791 0x81, 0x00, // lag global[0]
5792 0x4a, 0x06, // send 06
5793 0x36, // push
5794 0x35, 0x1e, // ldi 1Eh
5795 0x1e, // gt?
5796 0x31, 0xdb, // bnt [jump to end of step 1 code]
5797 0x38, PATCH_SELECTOR16(setCycle), // pushi setCycle
5798 0x78, // push1
5799 0x51, 0x18, // class Walk
5800 0x36, // push
5801 0x38, PATCH_SELECTOR16(setMotion), // pushi setMotion
5802 0x39, 0x04, // pushi 04
5803 0x51, 0x24, // class PolyPath
5804 0x36, // push
5805 0x39, 0x04, // pushi 04
5806 0x76, // push0
5807 0x72, PATCH_UINT16(0x0f4e), // lofsa aAppleMan
5808 0x4a, 0x04, // send 04
5809 0x36, // push
5810 0x35, 0x1d, // ldi 1Dh
5811 0x02, // add
5812 0x36, // push
5813 0x39, 0x03, // pushi 03
5814 0x76, // push0
5815 0x72, PATCH_UINT16(0x0f4e), // lofsa aAppleMan
5816 0x4a, 0x04, // send 04
5817 0x36, // push
5818 0x7c, // pushSelf
5819 0x81, 0x00, // lag global[0]
5820 0x4a, 0x12, // send 12h
5821 PATCH_END
5822 };
5823
5824 // script, description, signature patch
5825 static const SciScriptPatcherEntry larry1Signatures[] = {
5826 { true, 300, "Spanish: buy apple from barrel man", 1, larry1SignatureBuyApple, larry1PatchBuyApple },
5827 SCI_SIGNATUREENTRY_TERMINATOR
5828 };
5829
5830 // ===========================================================================
5831 // Leisure Suit Larry 2
5832 // On the plane, Larry is able to wear the parachute. This grants 4 points.
5833 // In early versions of LSL2, it was possible to get "unlimited" points by
5834 // simply wearing it multiple times.
5835 // They fixed it in later versions by remembering, if the parachute was already
5836 // used before.
5837 // But instead of adding it properly, it seems they hacked the script / forgot
5838 // to replace script 0 as well, which holds information about how many global
5839 // variables are allocated at the start of the game.
5840 // The script tries to read an out-of-bounds global variable, which somewhat
5841 // "worked" in SSCI, but ScummVM/SCI doesn't allow that.
5842 // That's why those points weren't granted here at all.
5843 // We patch to use global[5a], which seems to be unused in the whole game.
5844 // Applies to at least: English floppy
5845 // Responsible method: rm63Script::handleEvent
5846 // Fixes bug: #6346
5847 static const uint16 larry2SignatureWearParachutePoints[] = {
5848 0x35, 0x01, // ldi 01
5849 0xa1, SIG_MAGICDWORD, 0x8e, // sag global[8e]
5850 0x80, SIG_UINT16(0x01e0), // lag global[1e0]
5851 0x18, // not
5852 0x30, SIG_UINT16(0x000f), // bnt [don't give points]
5853 0x35, 0x01, // ldi 01
5854 0xa0, 0xe0, 0x01, // sag global[1e0]
5855 SIG_END
5856 };
5857
5858 static const uint16 larry2PatchWearParachutePoints[] = {
5859 PATCH_ADDTOOFFSET(+4),
5860 0x80, PATCH_UINT16(0x005a), // lag global[5a]
5861 PATCH_ADDTOOFFSET(+6),
5862 0xa0, PATCH_UINT16(0x005a), // sag global[5a]
5863 PATCH_END
5864 };
5865
5866 // script, description, signature patch
5867 static const SciScriptPatcherEntry larry2Signatures[] = {
5868 { true, 63, "plane: no points for wearing parachute", 1, larry2SignatureWearParachutePoints, larry2PatchWearParachutePoints },
5869 SCI_SIGNATUREENTRY_TERMINATOR
5870 };
5871
5872 // ===========================================================================
5873 // Leisure Suit Larry 5
5874 // In Miami the player can call the green card telephone number and get
5875 // green card including limo at the same time in the English 1.000 PC release.
5876 // This results later in a broken game in case the player doesn't read
5877 // the second telephone number for the actual limousine service, because
5878 // in that case it's impossible for the player to get back to the airport.
5879 //
5880 // We disable the code, that is responsible to make the limo arrive.
5881 //
5882 // This bug was fixed in the European (dual language) versions of the game.
5883 //
5884 // Applies to at least: English PC floppy (1.000)
5885 // Responsible method: sPhone::changeState(40)
5886 static const uint16 larry5SignatureGreenCardLimoBug[] = {
5887 0x7a, // push2
5888 SIG_MAGICDWORD,
5889 0x39, 0x07, // pushi 07
5890 0x39, 0x0c, // pushi 0Ch
5891 0x45, 0x0a, 0x04, // callb [export 10 of script 0], 04
5892 0x78, // push1
5893 0x39, 0x26, // pushi 26h (limo arrived flag)
5894 0x45, 0x07, 0x02, // callb [export 7 of script 0], 02 (sets flag)
5895 SIG_END
5896 };
5897
5898 static const uint16 larry5PatchGreenCardLimoBug[] = {
5899 PATCH_ADDTOOFFSET(+8),
5900 0x34, PATCH_UINT16(0x0000), // ldi 0000 (dummy)
5901 0x34, PATCH_UINT16(0x0000), // ldi 0000 (dummy)
5902 PATCH_END
5903 };
5904
5905 // In one of the conversations near the end (to be exact - room 380 and the text
5906 // about using champagne on Reverse Biaz - only used when you actually did that
5907 // in the game), the German text is too large, causing the textbox to get too large.
5908 // Because of that the talking head of Patti is drawn over the textbox. A translation oversight.
5909 // Applies to at least: German floppy
5910 // Responsible method: none, position of talker object on screen needs to get modified
5911 static const uint16 larry5SignatureGermanEndingPattiTalker[] = {
5912 SIG_MAGICDWORD,
5913 SIG_UINT16(0x006e), // object pattiTalker::x (110)
5914 SIG_UINT16(0x00b4), // object pattiTalker::y (180)
5915 SIG_ADDTOOFFSET(+469), // verify that it's really the German version
5916 0x59, 0x6f, 0x75, // (object name) "You"
5917 0x23, 0x47, 0x44, 0x75, // "#GDu"
5918 SIG_END
5919 };
5920
5921 static const uint16 larry5PatchGermanEndingPattiTalker[] = {
5922 PATCH_UINT16(0x005a), // object pattiTalker::x (90)
5923 PATCH_END
5924 };
5925
5926 // script, description, signature patch
5927 static const SciScriptPatcherEntry larry5Signatures[] = {
5928 { true, 280, "English-only: fix green card limo bug", 1, larry5SignatureGreenCardLimoBug, larry5PatchGreenCardLimoBug },
5929 { true, 380, "German-only: Enlarge Patti Textbox", 1, larry5SignatureGermanEndingPattiTalker, larry5PatchGermanEndingPattiTalker },
5930 SCI_SIGNATUREENTRY_TERMINATOR
5931 };
5932
5933 // ===========================================================================
5934 // This is called on every death dialog. Problem is at least the German
5935 // version of lsl6 gets title text that is far too long for the
5936 // available temp space resulting in temp space corruption. This patch
5937 // moves the title text around, so this overflow doesn't happen anymore. We
5938 // would otherwise get a crash calling for invalid views (this happens of
5939 // course also in sierra sci).
5940 // Applies to at least: German PC-CD
5941 // Responsible method: unknown
5942 static const uint16 larry6SignatureDeathDialog[] = {
5943 SIG_MAGICDWORD,
5944 0x3e, SIG_UINT16(0x0133), // link 0133 (offset 0x20)
5945 0x35, 0xff, // ldi ff
5946 0xa3, 0x00, // sal local[0]
5947 SIG_ADDTOOFFSET(+680), // ...
5948 0x8f, 0x01, // lsp param[1] (offset 0x2cf)
5949 0x7a, // push2
5950 0x5a, SIG_UINT16(0x0004), SIG_UINT16(0x010e), // lea temp[010e]
5951 0x36, // push
5952 0x43, 0x7c, 0x0e, // callk Message[7c], 0e
5953 SIG_ADDTOOFFSET(+90), // ...
5954 0x38, SIG_UINT16(0x00d6), // pushi 00d6 (offset 0x335)
5955 0x78, // push1
5956 0x5a, SIG_UINT16(0x0004), SIG_UINT16(0x010e), // lea temp[010e]
5957 0x36, // push
5958 SIG_ADDTOOFFSET(+76), // ...
5959 0x38, SIG_UINT16(0x00cd), // pushi 00cd (offset 0x38b)
5960 0x39, 0x03, // pushi 03
5961 0x5a, SIG_UINT16(0x0004), SIG_UINT16(0x010e), // lea temp[010e]
5962 0x36,
5963 SIG_END
5964 };
5965
5966 static const uint16 larry6PatchDeathDialog[] = {
5967 0x3e, 0x00, 0x02, // link 0200
5968 PATCH_ADDTOOFFSET(+687),
5969 0x5a, PATCH_UINT16(0x0004), PATCH_UINT16(0x0140), // lea temp[0140]
5970 PATCH_ADDTOOFFSET(+98),
5971 0x5a, PATCH_UINT16(0x0004), PATCH_UINT16(0x0140), // lea temp[0140]
5972 PATCH_ADDTOOFFSET(+82),
5973 0x5a, PATCH_UINT16(0x0004), PATCH_UINT16(0x0140), // lea temp[0140]
5974 PATCH_END
5975 };
5976
5977 // script, description, signature patch
5978 static const SciScriptPatcherEntry larry6Signatures[] = {
5979 { true, 82, "death dialog memory corruption", 1, larry6SignatureDeathDialog, larry6PatchDeathDialog },
5980 SCI_SIGNATUREENTRY_TERMINATOR
5981 };
5982
5983 #ifdef ENABLE_SCI32
5984 #pragma mark -
5985 #pragma mark Leisure Suit Larry 6 Hires
5986
5987 // When entering room 270 (diving board) from room 230, a typo in the game
5988 // script means that `setScale` is called accidentally instead of `setScaler`.
5989 // In SSCI this did not do much because the first argument happened to be
5990 // smaller than the y-position of `ego`, but in ScummVM the first argument is
5991 // larger and so a debug message "y value less than vanishingY" is displayed.
5992 static const uint16 larry6HiresSetScaleSignature[] = {
5993 SIG_MAGICDWORD,
5994 0x38, SIG_SELECTOR16(setScale), // pushi setScale ($14b)
5995 0x38, SIG_UINT16(0x0005), // pushi 5
5996 0x51, 0x2c, // class Scaler
5997 SIG_END
5998 };
5999
6000 static const uint16 larry6HiresSetScalePatch[] = {
6001 0x38, PATCH_SELECTOR16(setScaler), // pushi setScaler ($14f)
6002 PATCH_END
6003 };
6004
6005 // The init code that runs when LSL6hires starts up unconditionally resets the
6006 // master music volume to 12 (and the volume dial to 11), but the game should
6007 // always use the volume stored in ScummVM.
6008 // Applies to at least: English CD
6009 // Fixes bug: #9700
6010 static const uint16 larry6HiresVolumeResetSignature[] = {
6011 SIG_MAGICDWORD,
6012 0x35, 0x0b, // ldi $0b
6013 0xa1, 0xc2, // sag global[$c2]
6014 SIG_END
6015 };
6016
6017 static const uint16 larry6HiresVolumeResetPatch[] = {
6018 0x32, PATCH_UINT16(0x0001), // jmp 1 [past volume change]
6019 PATCH_END
6020 };
6021
6022 // script, description, signature patch
6023 static const SciScriptPatcherEntry larry6HiresSignatures[] = {
6024 { true, 71, "disable volume reset on startup (1/2)", 1, sci2VolumeResetSignature, sci2VolumeResetPatch },
6025 { true, 71, "disable volume reset on startup (2/2)", 1, larry6HiresVolumeResetSignature, larry6HiresVolumeResetPatch },
6026 { true, 270, "fix incorrect setScale call", 1, larry6HiresSetScaleSignature, larry6HiresSetScalePatch },
6027 { true, 64908, "disable video benchmarking", 1, sci2BenchmarkSignature, sci2BenchmarkPatch },
6028 { true, 64990, "increase number of save games (1/2)", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
6029 { true, 64990, "increase number of save games (2/2)", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
6030 { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
6031 SCI_SIGNATUREENTRY_TERMINATOR
6032 };
6033
6034 #pragma mark -
6035 #pragma mark Leisure Suit Larry 7
6036
6037 // The init code that runs when LSL7 starts up unconditionally resets the audio
6038 // volumes to defaults, but the game should always use the volume stored in
6039 // ScummVM. This patch is basically identical to the patch for Torin, except
6040 // that they left line numbers in the LSL7 scripts and changed the music volume.
6041 // Applies to at least: English CD
6042 static const uint16 larry7VolumeResetSignature1[] = {
6043 SIG_MAGICDWORD,
6044 0x35, 0x41, // ldi $41
6045 0xa1, 0xe3, // sag global[$e3] (music volume)
6046 0x7e, SIG_ADDTOOFFSET(+2), // (line whatever)
6047 0x35, 0x3c, // ldi $3c
6048 0xa1, 0xe4, // sag global[$e4] (sfx volume)
6049 0x7e, SIG_ADDTOOFFSET(+2), // (line whatever)
6050 0x35, 0x64, // ldi $64
6051 0xa1, 0xe5, // sag global[$e5] (speech volume)
6052 SIG_END
6053 };
6054
6055 static const uint16 larry7VolumeResetPatch1[] = {
6056 0x33, 0x10, // jmp [past volume resets]
6057 PATCH_END
6058 };
6059
6060 // The init code that runs when LSL7 starts up unconditionally resets the
6061 // audio volumes to values stored in larry7.prf, but the game should always use
6062 // the volume stored in ScummVM. This patch is basically identical to the patch
6063 // for Torin, except that they left line numbers in the LSL7 scripts.
6064 // Applies to at least: English CD
6065 static const uint16 larry7VolumeResetSignature2[] = {
6066 SIG_MAGICDWORD,
6067 0x38, SIG_SELECTOR16(readWord), // pushi readWord
6068 0x76, // push0
6069 SIG_ADDTOOFFSET(+6), // ...
6070 0xa1, 0xe3, // sag global[$e3] (music volume)
6071 SIG_ADDTOOFFSET(+3), // (line whatever)
6072 SIG_ADDTOOFFSET(+10), // ...
6073 0xa1, 0xe4, // sag global[$e4] (sfx volume)
6074 SIG_ADDTOOFFSET(+3), // (line whatever)
6075 SIG_ADDTOOFFSET(+10), // ...
6076 0xa1, 0xe5, // sag global[$e5] (speech volume)
6077 SIG_END
6078 };
6079
6080 static const uint16 larry7VolumeResetPatch2[] = {
6081 PATCH_ADDTOOFFSET(+10),
6082 0x18, 0x18, // (waste bytes)
6083 PATCH_ADDTOOFFSET(+3), // (line whatever)
6084 PATCH_ADDTOOFFSET(+10), // ...
6085 0x18, 0x18, // (waste bytes)
6086 PATCH_ADDTOOFFSET(+3), // (line whatever)
6087 PATCH_ADDTOOFFSET(+10), // ...
6088 0x18, 0x18, // (waste bytes)
6089 PATCH_END
6090 };
6091
6092 // In room 540 of Leisure Suit Larry 7, when using the cheese maker,
6093 // `soMakeCheese::changeState(6)` incorrectly pushes `self` as the end cel
6094 // instead of a cel number to the End cycler. In SSCI, this bad argument would
6095 // get corrected down to the final cel in the loop by `CycleCueList::init`, but
6096 // because ScummVM currently always sorts numbers higher than objects, the
6097 // comparison fails and the cel number is not corrected, so the cycler never
6098 // calls back and the game softlocks.
6099 // Here, we fix the call so a proper cel number is given for the second argument
6100 // instead of a bogus object pointer.
6101 //
6102 // Applies to at least: English PC-CD, German PC-CD
6103 static const uint16 larry7MakeCheeseCyclerSignature[] = {
6104 0x38, SIG_UINT16(0x04), // pushi 4
6105 0x51, 0xc4, // class End
6106 0x36, // push
6107 SIG_MAGICDWORD,
6108 0x7c, // pushSelf
6109 0x39, 0x04, // pushi 4
6110 0x7c, // pushSelf
6111 SIG_END
6112 };
6113
6114 static const uint16 larry7MakeCheeseCyclerPatch[] = {
6115 0x39, 0x04, // pushi 4 - save 1 byte
6116 0x51, 0xc4, // class End
6117 0x36, // push
6118 0x7c, // pushSelf
6119 0x39, 0x04, // pushi 4
6120 0x39, 0x10, // pushi $10 (last cel of view 54007, loop 0)
6121 PATCH_END
6122 };
6123
6124 // During the cheese maker cutscene, `soMakeCheese::changeState(2)` sets the
6125 // priority of ego to 500 to draw him over the cheese maker, but this is also
6126 // above the guillotine (view 54000, cel 7, priority 400), so ego gets
6127 // incorrectly drawn on top of the guillotine as well. The cheese maker has a
6128 // priority of 373, so use priority 374 instead of 500.
6129 // Applies to at least: English PC-CD, German PC-CD
6130 // Responsible method: soMakeCheese::changeState(2) in script 540
6131 static const uint16 larry7MakeCheesePrioritySignature[] = {
6132 0x38, SIG_SELECTOR16(setPri), // pushi setPri
6133 SIG_MAGICDWORD,
6134 0x78, // push1
6135 0x38, SIG_UINT16(0x01f4), // pushi 500
6136 SIG_END
6137 };
6138
6139 static const uint16 larry7MakeCheesePriorityPatch[] = {
6140 PATCH_ADDTOOFFSET(+4), // pushi setPri, push1
6141 0x38, PATCH_UINT16(0x0176), // pushi 374
6142 PATCH_END
6143 };
6144
6145 // LSL7 tries to reset the message type twice at startup, first with a default
6146 // value in script 0, then with a stored value from larry7.prf (if that file
6147 // exists) or the same default value (if it does not) in script 64000. Since
6148 // message type sync relies on the game only setting this value once at startup,
6149 // we must stop the second attempt or the value from ScummVM will be
6150 // overwritten.
6151 // Applies to at least: English CD
6152 static const uint16 larry7MessageTypeResetSignature[] = {
6153 SIG_MAGICDWORD,
6154 0x35, 0x02, // ldi 2
6155 0xa1, 0x5a, // sag global[$5a]
6156 SIG_END
6157 };
6158
6159 static const uint16 larry7MessageTypeResetPatch[] = {
6160 0x33, 0x02, // jmp [past reset]
6161 PATCH_END
6162 };
6163
6164 // script, description, signature patch
6165 static const SciScriptPatcherEntry larry7Signatures[] = {
6166 { true, 0, "disable message type reset on startup", 1, larry7MessageTypeResetSignature, larry7MessageTypeResetPatch },
6167 { true, 540, "fix make cheese cutscene (cycler)", 1, larry7MakeCheeseCyclerSignature, larry7MakeCheeseCyclerPatch },
6168 { true, 540, "fix make cheese cutscene (priority)", 1, larry7MakeCheesePrioritySignature, larry7MakeCheesePriorityPatch },
6169 { true, 64000, "disable volume reset on startup (1/2)", 1, larry7VolumeResetSignature1, larry7VolumeResetPatch1 },
6170 { true, 64000, "disable volume reset on startup (2/2)", 1, larry7VolumeResetSignature2, larry7VolumeResetPatch2 },
6171 { true, 64866, "increase number of save games", 1, torinLarry7NumSavesSignature, torinLarry7NumSavesPatch },
6172 SCI_SIGNATUREENTRY_TERMINATOR
6173 };
6174
6175 #endif
6176
6177 // ===========================================================================
6178 // Laura Bow 1 - Colonel's Bequest
6179 //
6180 // This is basically just a broken easter egg in Colonel's Bequest.
6181 // A plane can show up in room 4, but that only happens really rarely.
6182 // Anyway the Sierra developer seems to have just entered the wrong loop,
6183 // which is why the statue view is used instead (loop 0).
6184 // We fix it to use the correct loop.
6185 //
6186 // This is only broken in the PC version. It was fixed for Amiga + Atari ST.
6187 //
6188 // Credits to OmerMor, for finding it.
6189 //
6190 // Applies to at least: English PC Floppy
6191 // Responsible method: room4::init
6192 static const uint16 laurabow1SignatureEasterEggViewFix[] = {
6193 0x78, // push1
6194 0x76, // push0
6195 SIG_MAGICDWORD,
6196 0x38, SIG_SELECTOR16(setLoop), // pushi setLoop
6197 0x78, // push1
6198 0x39, 0x03, // pushi 3 (loop 3, view only has 3 loops)
6199 SIG_END
6200 };
6201
6202 static const uint16 laurabow1PatchEasterEggViewFix[] = {
6203 PATCH_ADDTOOFFSET(+7),
6204 0x02, // (change loop to 2)
6205 PATCH_END
6206 };
6207
6208 // When oiling the armor or opening the visor of the armor, the scripts first
6209 // check if Laura/ego is near the armor and if she is not, they will move her
6210 // to the armor. After that, further code is executed.
6211 //
6212 // The current location is checked by a ego::inRect() call.
6213 //
6214 // The given rect for the inRect call inside openVisor::changeState was made
6215 // larger for Atari ST/Amiga versions. We change the PC version to use the
6216 // same rect.
6217 //
6218 // Additionally, the coordinate that Laura is moved to (152, 107) may not be
6219 // reachable depending on where Laura was when "use oil on helmet of armor"
6220 // or "open visor of armor" got entered. Bad coordinates such as (82, 110),
6221 // cause collisions and effectively an endless loop, effectively freezing the
6222 // game. The user is only able to restore a previous game.
6223 //
6224 // We change the destination coordinate to (152, 110), which seems to be
6225 // reachable all the time.
6226 //
6227 // The following patch fixes the rect for the PC version of the game.
6228 //
6229 // Applies to at least: English PC Floppy
6230 // Responsible method: openVisor::changeState (script 37)
6231 // Fixes bug: #7119
6232 static const uint16 laurabow1SignatureArmorOpenVisorFix[] = {
6233 0x39, 0x04, // pushi 04
6234 SIG_MAGICDWORD,
6235 0x39, 0x6a, // pushi 6a (106d)
6236 0x38, SIG_UINT16(0x0096), // pushi 0096 (150d)
6237 0x39, 0x6c, // pushi 6c (108d)
6238 0x38, SIG_UINT16(0x0098), // pushi 0098 (152d)
6239 SIG_END
6240 };
6241
6242 static const uint16 laurabow1PatchArmorOpenVisorFix[] = {
6243 PATCH_ADDTOOFFSET(+2),
6244 0x39, 0x68, // pushi 68 (104d) (-2)
6245 0x38, PATCH_UINT16(0x0094), // pushi 0094 (148d) (-2)
6246 0x39, 0x6f, // pushi 6f (111d) (+3)
6247 0x38, PATCH_UINT16(0x009a), // pushi 009a (154d) (+2)
6248 PATCH_END
6249 };
6250
6251 // This here fixes the destination coordinate (exact details are above).
6252 //
6253 // Applies to at least: English PC Floppy, English Atari ST Floppy, English Amiga Floppy
6254 // Responsible method: openVisor::changeState, oiling::changeState (script 37)
6255 // Fixes bug: #7119
6256 static const uint16 laurabow1SignatureArmorMoveToFix[] = {
6257 SIG_MAGICDWORD,
6258 0x36, // push
6259 0x39, 0x6b, // pushi 6B (107d)
6260 0x38, SIG_UINT16(0x0098), // pushi 98 (152d)
6261 0x7c, // pushSelf
6262 0x81, 0x00, // lag global[0]
6263 SIG_END
6264 };
6265
6266 static const uint16 laurabow1PatchArmorMoveToFix[] = {
6267 PATCH_ADDTOOFFSET(+1),
6268 0x39, 0x6e, // pushi 6E (110d) - adjust x, so that no collision can occur anymore
6269 PATCH_END
6270 };
6271
6272 // In some cases like for example when the player oils the arm of the armor,
6273 // command input stays disabled, even when the player exits fast enough, so
6274 // that Laura doesn't die.
6275 //
6276 // This is caused by the scripts only enabling control (directional movement),
6277 // but do not enable command input as well.
6278 //
6279 // This bug also happens, when using the original interpreter. It was fixed for
6280 // the Atari ST + Amiga versions of the game.
6281 //
6282 // Applies to at least: English PC Floppy
6283 // Responsible method: 2nd subroutine in script 37, called by oiling::changeState(7)
6284 // Fixes bug: #7154
6285 static const uint16 laurabow1SignatureArmorOilingArmFix[] = {
6286 0x38, SIG_UINT16(0x0089), // pushi 89h
6287 0x76, // push0
6288 SIG_MAGICDWORD,
6289 0x72, SIG_UINT16(0x1a5c), // lofsa "Can" - offsets are not skipped to make sure only the PC version gets patched
6290 0x4a, 0x04, // send 04
6291 0x38, SIG_UINT16(0x0089), // pushi 89h
6292 0x76, // push0
6293 0x72, SIG_UINT16(0x19a1), // lofsa "Visor"
6294 0x4a, 0x04, // send 04
6295 0x38, SIG_UINT16(0x0089), // pushi 89h
6296 0x76, // push0
6297 0x72, SIG_UINT16(0x194a), // lofsa "note"
6298 0x4a, 0x04, // send 04
6299 0x38, SIG_UINT16(0x0089), // pushi 89h
6300 0x76, // push0
6301 0x72, SIG_UINT16(0x18f3), // lofsa "valve"
6302 0x4a, 0x04, // send 04
6303 0x8b, 0x34, // lsl local[34h]
6304 0x35, 0x02, // ldi 02
6305 0x1c, // ne?
6306 0x30, SIG_UINT16(0x0014), // bnt [to ret]
6307 0x8b, 0x34, // lsl local[34h]
6308 0x35, 0x05, // ldi 05
6309 0x1c, // ne?
6310 0x30, SIG_UINT16(0x000c), // bnt [to ret]
6311 0x8b, 0x34, // lsl local[34h]
6312 0x35, 0x06, // ldi 06
6313 0x1c, // ne?
6314 0x30, SIG_UINT16(0x0004), // bnt [to ret]
6315 // followed by code to call script 0 export to re-enable controls and call setMotion
6316 SIG_END
6317 };
6318
6319 static const uint16 laurabow1PatchArmorOilingArmFix[] = {
6320 PATCH_ADDTOOFFSET(+3), // skip over pushi 89h
6321 0x3c, // dup
6322 0x3c, // dup
6323 0x3c, // dup
6324 // saves a total of 6 bytes
6325 0x76, // push0
6326 0x72, PATCH_UINT16(0x1a59), // lofsa "Can"
6327 0x4a, 0x04, // send 04
6328 0x76, // push0
6329 0x72, PATCH_UINT16(0x19a1), // lofsa "Visor"
6330 0x4a, 0x04, // send 04
6331 0x76, // push0
6332 0x72, PATCH_UINT16(0x194d), // lofsa "note"
6333 0x4a, 0x04, // send 04
6334 0x76, // push0
6335 0x72, PATCH_UINT16(0x18f9), // lofsa "valve" 18f3
6336 0x4a, 0x04, // send 04
6337 // new code to enable input as well, needs 9 spare bytes
6338 0x38, PATCH_UINT16(0x00e2), // pushi canInput
6339 0x78, // push1
6340 0x78, // push1
6341 0x51, 0x2b, // class User
6342 0x4a, 0x06, // send 06 -> call User::canInput(1)
6343 // original code, but changed a bit to save some more bytes
6344 0x8b, 0x34, // lsl local[34h]
6345 0x35, 0x02, // ldi 02
6346 0x04, // sub
6347 0x31, 0x12, // bnt [to ret]
6348 0x36, // push
6349 0x35, 0x03, // ldi 03
6350 0x04, // sub
6351 0x31, 0x0c, // bnt [to ret]
6352 0x78, // push1
6353 0x1a, // eq?
6354 0x2f, 0x08, // bt [to ret]
6355 // saves 7 bytes, we only need 3, so waste 4 bytes
6356 0x35, 0x00, // ldi 0
6357 0x35, 0x00, // ldi 0
6358 PATCH_END
6359 };
6360
6361 // Jeeves lights the chapel candles (room 58) in act 2 but they don't stay
6362 // lit when re-entering until the next act. This is due to Room58:init
6363 // incorrectly testing the global variable that tracks Jeeves' act 2 state.
6364 //
6365 // We fix this by changing the test from if global[155] equals 11, which it
6366 // never does, to if it's greater than 11. The global is set to 12 in
6367 // lightCandles:changeState(11) and it continues to increment as Jeeves'
6368 // chore sequence progresses, ending with 17.
6369 //
6370 // Applies to: DOS, Amiga, Atari ST
6371 // Responsible method: Room58:init
6372 // Fixes bug: #10743
6373 static const uint16 laurabow1SignatureChapelCandlesPersistence[] = {
6374 SIG_MAGICDWORD,
6375 0x89, 0x9b, // lsg global[155] [ Jeeves' act 2 state ]
6376 0x35, 0x0b, // ldi b
6377 0x1a, // eq?
6378 SIG_END
6379 };
6380
6381 static const uint16 laurabow1PatchChapelCandlesPersistence[] = {
6382 PATCH_ADDTOOFFSET(+4),
6383 0x1e, // gt?
6384 PATCH_END
6385 };
6386
6387 // LB1 DOS doesn't acknowledge Lillian's presence in room 44 when she's sitting
6388 // on the bed in act 4. Look, talk, etc respond that she's not there.
6389 // This is due to not setting global[195] which tracks who is in the room.
6390 // We fix this by setting the global as Amiga and Atari ST versions do.
6391 //
6392 // Applies to: DOS only
6393 // Responsible method: Room44:init
6394 // Fixes bug: #10742
6395 static const uint16 laurabow1SignatureLillianBedFix[] = {
6396 SIG_MAGICDWORD,
6397 0x72, SIG_UINT16(0x10f8), // lofsa suit2 [ only matches DOS version ]
6398 0x4a, 0x14, // send 14
6399 SIG_ADDTOOFFSET(+8),
6400 0x89, 0x76, // lsg global[118]
6401 0x35, 0x02, // ldi 2
6402 0x12, // and
6403 0x30, SIG_UINT16(0x000d), // bnt d [ haven't seen Lillian in study ]
6404 0x35, 0x01, // ldi 1
6405 SIG_END
6406 };
6407
6408 static const uint16 laurabow1PatchLillianBedFix[] = {
6409 PATCH_ADDTOOFFSET(+13),
6410 0x81, 0x76, // lag global[118]
6411 0x7a, // push2
6412 0x12, // and
6413 0x31, 0x0f, // bnt f [ haven't seen Lillian in study ]
6414 0x35, 0x20, // ldi 20 [ Lillian ]
6415 0xa1, 0xc3, // sag global[195] [ set Lillian as in the room ]
6416 PATCH_END
6417 };
6418
6419 // When you tell Lilly about Gertie in room 35, Lilly will then walk to the
6420 // left and off the screen. If Laura (ego) is in the way, the whole game will
6421 // basically block and you won't be able to do anything except saving or
6422 // restoring the game.
6423 //
6424 // If this happened already, the player can enter "send Lillian ignoreActors 1"
6425 // inside the debugger to fix this situation.
6426 //
6427 // This issue is very difficult to solve, because Lilly also walks diagonally
6428 // after walking to the left right under the kitchen table. This means that
6429 // even if we added a few more rectangle checks, there could still be spots,
6430 // where the game would block.
6431 //
6432 // Also the mover "PathOut" is used for Lillian instead of the regular
6433 // "MoveTo", which would avoid other actors by itself.
6434 //
6435 // So instead we set Lilly to ignore other actors during that cutscene, which
6436 // is the least invasive solution.
6437 //
6438 // Applies to at least: English PC Floppy, English Amiga Floppy, English Atari ST Floppy
6439 // Responsible method: goSee::changeState(1) in script 236
6440 // Fixes bug: (happened during GOG Let's Play)
6441 static const uint16 laurabow1SignatureTellLillyAboutGerieBlockingFix1[] = {
6442 0x7a, // puah2
6443 SIG_MAGICDWORD,
6444 0x38, SIG_UINT16(0x00c1), // pushi 00C1h
6445 0x38, SIG_UINT16(0x008f), // pushi 008Fh
6446 0x38, SIG_SELECTOR16(ignoreActors), // pushi ignoreActors
6447 0x78, // push1
6448 0x76, // push0
6449 SIG_END
6450 };
6451
6452 static const uint16 laurabow1PatchTellLillyAboutGertieBlockingFix1[] = {
6453 PATCH_ADDTOOFFSET(+11), // skip over until push0
6454 0x78, // push1 (change push0 to push1)
6455 PATCH_END
6456 };
6457
6458 // a second patch to call Lillian::ignoreActors(1) on goSee::changeState(9) in script 236
6459 static const uint16 laurabow1SignatureTellLillyAboutGerieBlockingFix2[] = {
6460 0x3c, // dup
6461 0x35, 0x09, // ldi 09
6462 0x1a, // eq?
6463 0x30, SIG_UINT16(0x003f), // bnt [ret]
6464 0x39, SIG_ADDTOOFFSET(+1), // pushi view
6465 0x78, // push1
6466 0x38, SIG_UINT16(0x0203), // pushi 203h (515d)
6467 0x38, SIG_ADDTOOFFSET(+2), // pushi posn
6468 0x7a, // push2
6469 0x38, SIG_UINT16(0x00c9), // pushi C9h (201d)
6470 SIG_MAGICDWORD,
6471 0x38, SIG_UINT16(0x0084), // pushi 84h (132d)
6472 0x72, SIG_ADDTOOFFSET(+2), // lofsa Lillian (different offsets for different platforms)
6473 0x4a, 0x0e, // send 0Eh
6474 SIG_END
6475 };
6476
6477 static const uint16 laurabow1PatchTellLillyAboutGertieBlockingFix2[] = {
6478 0x38, PATCH_SELECTOR16(ignoreActors), // pushi ignoreActors
6479 0x78, // push1
6480 0x76, // push0
6481 0x33, 0x00, // ldi 00 (waste 2 bytes)
6482 PATCH_ADDTOOFFSET(+19), // skip over until send
6483 0x4a, 0x14, // send 14h
6484 PATCH_END
6485 };
6486
6487 // LB1 contains over 20 commands which lockup the game if entered while ego is
6488 // colliding with an obstacle. Opening and moving closets in room 43 are prime
6489 // examples. They are all symptoms of a global bug.
6490
6491 // Every cycle, CB1:doit checks to see if ego appears to be walking and blocked,
6492 // and if so it stops ego's motion and sets ego's view to 11 (standing). If ego
6493 // has just collided with an obstacle but is still displaying view 1 (walking)
6494 // when a command is entered which disables input and sets ego's motion without
6495 // setting an avoider then CB1:doit will stop that new motion at the start of
6496 // the next cycle and lockup the game. This occurs in part because CB1:doit
6497 // tests potentially stale values that the new motion hasn't yet had a chance
6498 // to set. Scripts which set an avoider aren't vulnerable because CB1:doit
6499 // won't stop ego if one is set. Other SCI games don't have this kind of code
6500 // in their Game's doit and so they don't have this bug.
6501
6502 // We fix this by clearing the kSignalHitObstacle flag whenever Act:setMotion is
6503 // called with a new motion. This causes CB1:doit's subsequent call to
6504 // ego:isBlocked to return false, instead of a stale true value, preventing
6505 // CB1:doit from stopping the new motion before it's had a chance to start.
6506 // Should the new motion actually be blocked then the interpreter will then set
6507 // the flag when processing the motion as usual. This patch closes the short
6508 // window during which this value is stale and CB1:doit tests it.
6509
6510 // Applies to: DOS, Amiga, Atari ST and occurs in Sierra's interpreter.
6511 // Responsible method: Act:setMotion
6512 // Fixes bug: #10733
6513 static const uint16 laurabow1SignatureObstacleCollisionLockupsFix[] = {
6514 SIG_MAGICDWORD,
6515 0x30, SIG_UINT16(0x002f), // bnt 2f
6516 0x38, SIG_UINT16(0x00a3), // pushi a3 [ startUpd ]
6517 0x76, // push0
6518 0x54, 0x04, // self 4
6519 0x7a, // push2 [ -info- ]
6520 0x76, // push0
6521 0x87, 0x01, // lap param[1]
6522 0x4a, 0x04, // send 4
6523 0x36, // push
6524 0x34, SIG_UINT16(0x8000), // ldi 8000
6525 0x12, // and
6526 0x30, SIG_UINT16(0x000a), // bnt a
6527 0x39, 0x56, // pushi 56 [ new ]
6528 0x76, // push0
6529 0x87, 0x01, // lap param[1]
6530 0x4a, 0x04, // send 4
6531 0x32, SIG_UINT16(0x0002), // jmp 2
6532 0x87, 0x01, // lap param[1]
6533 0x65, 0x4c, // aTop mover
6534 0x39, 0x57, // pushi 57 [ init ]
6535 0x78, // push1
6536 0x7c, // pushSelf
6537 0x59, 0x02, // &rest 2
6538 0x63, 0x4c, // pToa mover
6539 0x4a, 0x06, // send 6
6540 0x32, SIG_UINT16(0x0004), // jmp 4
6541 0x35, 0x00, // ldi 0
6542 SIG_END
6543 };
6544
6545 static const uint16 laurabow1PatchObstacleCollisionLockupsFix[] = {
6546 0x31, 0x32, // bnt 32 [ save 1 byte ]
6547
6548 0x63, 0x1c, // pToa signal
6549 0x38, PATCH_UINT16(0xfbff), // pushi fbff
6550 0x12, // and
6551 0x65, 0x1c, // aTop signal [ clear kSignalHitObstacle (0400) ]
6552
6553 0x38, PATCH_UINT16(0x00a3), // pushi a3 [ startUpd ]
6554 0x76, // push0
6555 0x54, 0x04, // self 4
6556 0x7a, // push2 [ -info- ]
6557 0x76, // push0
6558 0x87, 0x01, // lap param[1]
6559 0x4a, 0x04, // send 4
6560 0x38, PATCH_UINT16(0x8000), // pushi 8000 [ save 1 byte ]
6561 0x12, // and
6562 0x31, 0x09, // bnt 9 [ save 1 byte ]
6563 0x39, 0x56, // pushi 56 [ new ]
6564 0x76, // push0
6565 0x87, 0x01, // lap param[1]
6566 0x4a, 0x04, // send 4
6567 0x33, 0x02, // jmp 2 [ save 1 byte ]
6568 0x87, 0x01, // lap param[1]
6569 0x65, 0x4c, // aTop mover
6570 0x39, 0x57, // pushi 57 [ init ]
6571 0x78, // push1
6572 0x7c, // pushSelf
6573 0x59, 0x02, // &rest 2
6574 0x63, 0x4c, // pToa mover
6575 0x4a, 0x06, // send 6
6576 0x48, // ret [ save 4 bytes ]
6577 PATCH_END
6578 };
6579
6580 // Laura can get stuck walking up the attic stairs diagonally in room 47 and
6581 // lockup the game. This also occurs in the original. Room47:doit loads the
6582 // attic when ego is on control $10 at the base of the stairs and facing north.
6583 // Room47:handleEvent prevents the user from moving ego when on control $10.
6584 // Walking up the stairs diagonally puts ego in control $10 facing left or
6585 // right and so the room never changes and the user never regains control.
6586 //
6587 // We fix this by allowing ego to face any direction except south to trigger the
6588 // attic room change. This also fixes an edge case that allows walking through
6589 // the staircase wall into Clarence's room.
6590 //
6591 // Applies to: DOS, Amiga, Atari ST
6592 // Responsible method: Room47:doit
6593 // Fixes bug: #9949
6594 static const uint16 laurabow1SignatureAtticStairsLockupFix[] = {
6595 SIG_MAGICDWORD,
6596 0x39, SIG_SELECTOR8(loop), // pushi loop
6597 0x76, // push0
6598 0x81, 0x00, // lag global[0]
6599 0x4a, 0x04, // send 4 [ ego:loop? ]
6600 0x36, // push
6601 0x35, 0x03, // ldi 03 [ facing north ]
6602 0x1a, // eq?
6603 SIG_END
6604 };
6605
6606 static const uint16 laurabow1PatchAtticStairsLockupFix[] = {
6607 PATCH_ADDTOOFFSET(+8),
6608 0x35, 0x02, // ldi 02 [ facing south ]
6609 0x1c, // ne?
6610 PATCH_END
6611 };
6612
6613 // Laura can get stuck at the top of the left stairs in room 47 and lockup the
6614 // game. This also occurs in the original. There is a 30x2 control area at the
6615 // top of the stairs in which Room47:handleEvent prevents input. This assumes
6616 // that ego can't be interrupted when walking through the area, but there is a
6617 // notch in the left wall that ego can collide with, leaving ego stuck with
6618 // input disabled. The right wall doesn't have a notch.
6619 //
6620 // We fix this by allowing input at the top of the stairs. Up and down movements
6621 // are allowed when on the staircase's control area ($0200) and we extend that
6622 // to include the top of the stairs ($0800).
6623 //
6624 // Applies to: DOS, Amiga, Atari ST
6625 // Responsible method: Room47:handleEvent
6626 // Fixes bug #10879
6627 static const uint16 laurabow1SignatureLeftStairsLockupFix[] = {
6628 SIG_MAGICDWORD,
6629 0x34, SIG_UINT16(0x0200), // ldi 0200 [ left stairs ]
6630 0x1a, // eq? [ is ego entirely on the stairs? ]
6631 SIG_END
6632 };
6633
6634 static const uint16 laurabow1PatchLeftStairsLockupFix[] = {
6635 0x34, PATCH_UINT16(0x0a00), // ldi 0a00 [ left stairs | top of left stairs ]
6636 0x12, // and [ is ego touching the stairs or the top? ]
6637 PATCH_END
6638 };
6639
6640 // LB1's fingerprint copy protection randomly rejects the correct answer and may
6641 // even fail to draw a fingerprint. myCopy:init selects the fingerprint by
6642 // generating random loop and cel numbers for view 553, but it passes incorrect
6643 // ranges to kRandom. If kRandom returns the maximum value then the loop or cel
6644 // overflow and a different image is displayed than what was intended.
6645 //
6646 // We correct the ranges from 0-600 and 1-1000 to 0-599 and 0-999 so that
6647 // invalid cel 6 and loop 10 are never used after the script divides by 10.
6648 //
6649 // Applies to: DOS, Amiga, Atari ST
6650 // Responsible method: myCopy:init
6651 static const uint16 laurabow1SignatureCopyProtectionRandomFix[] = {
6652 0x38, SIG_UINT16(0x0258), // pushi 600d
6653 SIG_ADDTOOFFSET(+10),
6654 SIG_MAGICDWORD,
6655 0x78, // push1
6656 0x38, SIG_UINT16(0x03e8), // pushi 1000d
6657 SIG_END
6658 };
6659
6660 static const uint16 laurabow1PatchCopyProtectionRandomFix[] = {
6661 0x38, PATCH_UINT16(0x0257), // pushi 599d
6662 PATCH_ADDTOOFFSET(+10),
6663 0x76, // push0
6664 0x38, PATCH_UINT16(0x03e7), // pushi 999d
6665 PATCH_END
6666 };
6667
6668 // script, description, signature patch
6669 static const SciScriptPatcherEntry laurabow1Signatures[] = {
6670 { true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix },
6671 { true, 37, "armor open visor fix", 1, laurabow1SignatureArmorOpenVisorFix, laurabow1PatchArmorOpenVisorFix },
6672 { true, 37, "armor move to fix", 2, laurabow1SignatureArmorMoveToFix, laurabow1PatchArmorMoveToFix },
6673 { true, 37, "allowing input, after oiling arm", 1, laurabow1SignatureArmorOilingArmFix, laurabow1PatchArmorOilingArmFix },
6674 { true, 44, "lillian bed fix", 1, laurabow1SignatureLillianBedFix, laurabow1PatchLillianBedFix },
6675 { true, 47, "attic stairs lockup fix", 1, laurabow1SignatureAtticStairsLockupFix, laurabow1PatchAtticStairsLockupFix },
6676 { true, 47, "left stairs lockup fix", 3, laurabow1SignatureLeftStairsLockupFix, laurabow1PatchLeftStairsLockupFix },
6677 { true, 58, "chapel candles persistence", 1, laurabow1SignatureChapelCandlesPersistence, laurabow1PatchChapelCandlesPersistence },
6678 { true, 236, "tell Lilly about Gertie blocking fix 1/2", 1, laurabow1SignatureTellLillyAboutGerieBlockingFix1, laurabow1PatchTellLillyAboutGertieBlockingFix1 },
6679 { true, 236, "tell Lilly about Gertie blocking fix 2/2", 1, laurabow1SignatureTellLillyAboutGerieBlockingFix2, laurabow1PatchTellLillyAboutGertieBlockingFix2 },
6680 { true, 414, "copy protection random fix", 1, laurabow1SignatureCopyProtectionRandomFix, laurabow1PatchCopyProtectionRandomFix },
6681 { true, 998, "obstacle collision lockups fix", 1, laurabow1SignatureObstacleCollisionLockupsFix, laurabow1PatchObstacleCollisionLockupsFix },
6682 SCI_SIGNATUREENTRY_TERMINATOR
6683 };
6684
6685 // ===========================================================================
6686 // Laura Bow 2
6687 //
6688 // Moving away the painting in the room with the hidden safe is problematic
6689 // for the CD version of the game. safePic::doVerb gets triggered by the mouse-click.
6690 // This method sets local[0] as signal, which is only meant to get handled, when
6691 // the player clicks again to move the painting back. This signal is processed by
6692 // the room doit-script.
6693 // That doit-script checks safePic::cel to be not equal 0 and would then skip over
6694 // the "close painting" trigger code. On very fast computers this script may
6695 // get called too early (which is the case when running under ScummVM and when
6696 // running the game using Sierra SCI in DOS-Box with cycles 15000) and thinks
6697 // that it's supposed to move the painting back. Which then results in the painting
6698 // getting moved to its original position immediately (which means it won't be possible
6699 // to access the safe behind it).
6700 //
6701 // We patch the script, so that we check for cel to be not equal 4 (the final cel) and
6702 // we also reset the safePic-signal immediately as well.
6703 //
6704 // In the floppy version Laura's coordinates are checked directly in rm560::doit
6705 // and as soon as she moves, the painting will automatically move to its original position.
6706 // This is not the case for the CD version of the game. The painting will only "move" back,
6707 // when the player actually exits the room and re-enters.
6708 //
6709 // Applies to at least: English PC-CD
6710 // Responsible method: rm560::doit
6711 // Fixes bug: #6460
6712 static const uint16 laurabow2CDSignaturePaintingClosing[] = {
6713 0x39, 0x04, // pushi 04 (cel)
6714 0x76, // push0
6715 SIG_MAGICDWORD,
6716 0x7a, // push2
6717 0x38, SIG_UINT16(0x0231), // pushi 0231h (561)
6718 0x76, // push0
6719 0x43, 0x02, 0x04, // callk ScriptID, 04 (get export 0 of script 561)
6720 0x4a, 0x04, // send 04 (gets safePicture::cel)
6721 0x18, // not
6722 0x31, 0x21, // bnt [exit]
6723 0x38, SIG_UINT16(0x0283), // pushi 0283h
6724 0x76, // push0
6725 0x7a, // push2
6726 0x39, 0x20, // pushi 20
6727 0x76, // push0
6728 0x43, 0x02, 0x04, // callk ScriptID, 04 (get export 0 of script 32)
6729 0x4a, 0x04, // send 04 (get sHeimlich::room)
6730 0x36, // push
6731 0x81, 0x0b, // lag global[b] (current room)
6732 0x1c, // ne?
6733 0x31, 0x0e, // bnt [exit]
6734 0x35, 0x00, // ldi 00
6735 0xa3, 0x00, // sal local[0] (reset safePic signal)
6736 SIG_END
6737 };
6738
6739 static const uint16 laurabow2CDPatchPaintingClosing[] = {
6740 PATCH_ADDTOOFFSET(+2),
6741 0x3c, // dup (1 additional byte)
6742 0x76, // push0
6743 0x3c, // dup (1 additional byte)
6744 0xab, 0x00, // ssl local[0] (reset safePic signal)
6745 0x7a, // push2
6746 0x38, PATCH_UINT16(0x0231), // pushi 0231h (561)
6747 0x76, // push0
6748 0x43, 0x02, 0x04, // callk ScriptID, 04 (get export 0 of script 561)
6749 0x4a, 0x04, // send 04 (gets safePicture::cel)
6750 0x1a, // eq?
6751 0x31, 0x1d, // bnt [exit]
6752 0x38, PATCH_UINT16(0x0283), // pushi 0283h
6753 0x76, // push0
6754 0x7a, // push2
6755 0x39, 0x20, // pushi 20
6756 0x76, // push0
6757 0x43, 0x02, 0x04, // callk ScriptID, 04 (get export 0 of script 32)
6758 0x4a, 0x04, // send 04 (get sHeimlich::room)
6759 0x36, // push
6760 0x81, 0x0b, // lag global[b] (current room)
6761 0x1a, // eq? (2 opcodes changed, to save 2 bytes)
6762 0x2f, 0x0a, // bt [exit]
6763 PATCH_END
6764 };
6765
6766 // In the CD version the system menu is disabled for certain rooms. LB2::handsOff is called,
6767 // when leaving the room (and in other cases as well). This method remembers the disabled
6768 // icons of the icon bar. In the new room LB2::handsOn will get called, which then enables
6769 // all icons, but also disabled the ones, that were disabled before.
6770 //
6771 // Because of this behaviour certain rooms, that should have the system menu enabled, have
6772 // it disabled, when entering those rooms from rooms, where the menu is supposed to be
6773 // disabled.
6774 //
6775 // We patch this by injecting code into LB2::newRoom (which is called right after a room change)
6776 // and reset the global variable there, that normally holds the disabled buttons.
6777 //
6778 // This patch may cause side-effects and it's difficult to test, because it affects every room
6779 // in the game. At least for the intro, the speakeasy and plenty of rooms in the beginning it
6780 // seems to work correctly.
6781 //
6782 // Applies to at least: English PC-CD
6783 // Responsible method: LB2::newRoom, LB2::handsOff, LB2::handsOn
6784 // Fixes bug: #6440
6785 static const uint16 laurabow2CDSignatureFixProblematicIconBar[] = {
6786 SIG_MAGICDWORD,
6787 0x38, SIG_UINT16(0x00f1), // pushi 00f1 (disable) - hardcoded, we only want to patch the CD version
6788 0x76, // push0
6789 0x81, 0x45, // lag global[45]
6790 0x4a, 0x04, // send 04
6791 SIG_END
6792 };
6793
6794 static const uint16 laurabow2CDPatchFixProblematicIconBar[] = {
6795 0x35, 0x00, // ldi 00
6796 0xa1, 0x74, // sag global[74]
6797 0x35, 0x00, // ldi 00 (waste bytes)
6798 0x35, 0x00, // ldi 00
6799 PATCH_END
6800 };
6801
6802 // LB2 CD responds with the wrong message when asking Yvette about Tut in acts 3+.
6803 //
6804 // aYvette:doVerb(6) tests flag 134, which is set when Pippin dies, to determine
6805 // which Tut message to display but they got it backwards. One of the messages
6806 // has additional dialogue about Pippin's murder.
6807 //
6808 // This is a regression introduced by Sierra when they fixed a bug from the floppy
6809 // versions where asking Yvette about Tut in act 2 responds with the message
6810 // about Pippin's murder which hasn't occurred yet, bug #10723. Sierra correctly
6811 // fixed that in Yvette:doVerb in script 93, which applies to act 2, but then went
6812 // on to add incorrect code to aYvette:doVerb in script 90, which applies to the
6813 // later acts after the murder.
6814 //
6815 // We fix this by reversing the flag test so that the correct message is displayed.
6816 //
6817 // Applies to: CD version, at least English
6818 // Responsible method: aYvette:doVerb
6819 // Fixes bug: #10724
6820 static const uint16 laurabow2CDSignatureFixYvetteTutResponse[] = {
6821 SIG_MAGICDWORD,
6822 0x34, SIG_UINT16(0x010f), // ldi 010f [ tut ]
6823 0x1a, // eq? [ asked about tut? ]
6824 0x30, SIG_UINT16(0x0036), // bnt 0036
6825 0x78, // push1
6826 0x38, SIG_UINT16(0x0086), // pushi 0086 [ pippin-dead flag ]
6827 0x45, 0x02, 0x02, // callb [export 2 of script 0], 02 [ is pippin-dead flag set? ]
6828 0x30, SIG_UINT16(0x0016), // bnt 0016 [ pippin-dead message ]
6829 SIG_END
6830 };
6831
6832 static const uint16 laurabow2CDPatchFixYvetteTutResponse[] = {
6833 PATCH_ADDTOOFFSET(+14),
6834 0x2e, // bt (replace bnt)
6835 PATCH_END
6836 };
6837
6838 // When entering the main musem party room (w/ the golden Egyptian head), Laura
6839 // is walking a bit into the room automatically. If you press a mouse button
6840 // while this is happening, you will get stuck inside that room and won't be
6841 // able to exit it anymore.
6842 //
6843 // Users, who played the game w/ a previous version of ScummVM can simply enter
6844 // the debugger and then enter "send rm350 script 0:0", which will fix the
6845 // script state.
6846 //
6847 // This is caused by the user controls not being locked at that point. Pressing
6848 // a button will cause the cue from the PolyPath walker to never happen, which
6849 // then causes sEnterSouth to never dispose itself.
6850 //
6851 // User controls are locked in the previous room 335, but controls are unlocked
6852 // by frontDoor::cue. We do not want to change this, because it could have
6853 // side-effects. We instead add another LB2::handsOff call inside the script
6854 // responsible for Laura's walk into the room (sEnterSouth::changeState(0).
6855 //
6856 // Applies to at least: English PC-CD, English PC-Floppy, German PC-Floppy
6857 // Responsible method: sEnterSouth::changeState
6858 // Fixes bug: (no bug report, from GOG forum post)
6859 static const uint16 laurabow2SignatureMuseumPartyFixEnteringSouth1[] = {
6860 0x3c, // dup
6861 0x35, 0x00, // ldi 00
6862 0x1a, // eq?
6863 0x30, SIG_UINT16(0x0097), // bnt [state 1 code]
6864 SIG_ADDTOOFFSET(+141), // skip to end of follow-up code
6865 0x32, SIG_ADDTOOFFSET(+2), // jmp [ret] (0x008d for CD, 0x007d for floppy)
6866 0x35, 0x01, // ldi 01
6867 0x65, 0x1a, // aTop cycles
6868 0x32, SIG_ADDTOOFFSET(+2), // jmp [ret] (0x0086 for CD, 0x0076 for floppy)
6869 // state 1 code
6870 0x3c, // dup
6871 0x35, 0x01, // ldi 01
6872 0x1a, // eq?
6873 SIG_MAGICDWORD,
6874 0x31, 0x05, // bnt [state 2 code]
6875 0x35, 0x00, // ldi 00
6876 0x32, SIG_ADDTOOFFSET(+2), // jmp [ret] (0x007b for CD, 0x006b for floppy)
6877 // state 2 code
6878 0x3c, // dup
6879 SIG_END
6880 };
6881
6882 static const uint16 laurabow2PatchMuseumPartyFixEnteringSouth1[] = {
6883 0x2e, PATCH_UINT16(0x00a6), // bt [state 2 code] (we skip state 1, because it's a NOP anyways)
6884 // state 0 processing
6885 0x32, PATCH_UINT16(0x0097), // jmp 151d [after this ret]
6886 PATCH_ADDTOOFFSET(+149), // skip to end of follow-up code
6887 // save 1 byte by replacing jump to [ret] into straight toss/ret
6888 0x3a, // toss
6889 0x48, // ret
6890
6891 // additional code, that gets called right at the start of step 0 processing
6892 0x18, // not -- this here is where pushi handsOff will be inserted by the second patch
6893 0x18, // not offset and handsOff is different for floppy + CD, that's why we do this
6894 0x18, // not floppy also does not have a selector table, so we can't go by "handsOff" name
6895 0x18, // not
6896 0x76, // push0
6897 0x81, 0x01, // lag global[1]
6898 0x4a, 0x04, // send 04
6899 0x32, PATCH_UINT16(0xff5e), // jmp [back to start of step 0 processing]
6900 PATCH_END
6901 };
6902
6903 // Second patch, which only inserts pushi handsOff inside our new code. There
6904 // is no other way to do this except making 2 full patches for floppy + CD,
6905 // because handsOff/handsOn is not the same value between floppy + CD *and*
6906 // floppy doesn't even have a vocab, so we can't figure out the id by
6907 // ourselves.
6908 static const uint16 laurabow2SignatureMuseumPartyFixEnteringSouth2[] = {
6909 0x18, // our injected code
6910 0x18,
6911 0x18,
6912 SIG_ADDTOOFFSET(+92), // skip to the handsOn code, that we are interested in
6913 0x38, SIG_ADDTOOFFSET(+2), // pushi handsOn (0x0189 for CD, 0x024b for floppy)
6914 0x76, // push0
6915 0x81, 0x01, // lag global[1]
6916 0x4a, 0x04, // send 04
6917 0x38, SIG_ADDTOOFFSET(+2), // pushi 0274h
6918 SIG_MAGICDWORD,
6919 0x78, // push1
6920 0x38, SIG_UINT16(0x033f), // pushi 033f
6921 SIG_END
6922 };
6923
6924 static const uint16 laurabow2PatchMuseumPartyFixEnteringSouth2[] = {
6925 0x38, PATCH_GETORIGINALUINT16ADJUST(+96, -1), // pushi handsOff (@handsOn - 1)
6926 PATCH_END
6927 };
6928
6929 // Opening/Closing the east door in the pterodactyl room doesn't check, if it's
6930 // locked and will open/close the door internally even when it is.
6931 //
6932 // It will get wired shut later in the game by Laura Bow and will be "locked"
6933 // because of this. We patch in a check for the locked state. We also add
6934 // code, that will set the "locked" state in case our eastDoor-wired-global is
6935 // set. This makes the locked state effectively persistent.
6936 //
6937 // Applies to at least: English PC-CD, English PC-Floppy
6938 // Responsible method (CD): eastDoor::doVerb
6939 // Responsible method (Floppy): eastDoor::<noname300>
6940 // Fixes bug: #6458 (partly, see additional patch below)
6941 static const uint16 laurabow2SignatureFixWiredEastDoor[] = {
6942 0x30, SIG_UINT16(0x0022), // bnt [skip hand action]
6943 0x67, SIG_ADDTOOFFSET(+1), // pTos (CD: doorState, Floppy: state)
6944 0x35, 0x00, // ldi 00
6945 0x1a, // eq?
6946 0x31, 0x08, // bnt [close door code]
6947 0x78, // push1
6948 SIG_MAGICDWORD,
6949 0x39, 0x63, // pushi 63h
6950 0x45, 0x04, 0x02, // callb [export 4 of script 0], 02 (sets door-bitflag)
6951 0x33, 0x06, // jmp [super-code]
6952 0x78, // push1
6953 0x39, 0x63, // pushi 63h
6954 0x45, 0x03, 0x02, // callb [export 3 of script 0], 02 (resets door-bitflag)
6955 0x38, SIG_ADDTOOFFSET(+2), // pushi (CD: 011dh, Floppy: 012ch)
6956 0x78, // push1
6957 0x8f, 0x01, // lsp param[1]
6958 0x59, 0x02, // rest 02
6959 0x57, SIG_ADDTOOFFSET(+1), 0x06, // super (CD: LbDoor, Floppy: Door), 06
6960 0x33, 0x0b, // jmp [ret]
6961 SIG_END
6962 };
6963
6964 static const uint16 laurabow2PatchFixWiredEastDoor[] = {
6965 0x31, 0x23, // bnt [skip hand action] (saves 1 byte)
6966 0x81, 0x61, // lag global[97d] (get our eastDoor-wired-global)
6967 0x31, 0x04, // bnt [skip setting locked property]
6968 0x35, 0x01, // ldi 01
6969 0x65, 0x6a, // aTop locked (set eastDoor::locked to 1)
6970 0x63, 0x6a, // pToa locked (get eastDoor::locked)
6971 0x2f, 0x17, // bt [skip hand action]
6972 0x63, PATCH_GETORIGINALBYTE(+4), // pToa (CD: doorState, Floppy: state)
6973 0x78, // push1
6974 0x39, 0x63, // pushi 63h
6975 0x2f, 0x05, // bt [close door code]
6976 0x45, 0x04, 0x02, // callb [export 4 of script 0], 02 (sets door-bitflag)
6977 0x33, 0x0b, // jmp [super-code]
6978 0x45, 0x03, 0x02, // callb [export 3 of script 0], 02 (resets door-bitflag)
6979 0x33, 0x06, // jmp [super-code]
6980 PATCH_END
6981 };
6982
6983 // We patch in code, so that our eastDoor-wired-global will get set to 1.
6984 // This way the wired-state won't get lost when exiting room 430.
6985 //
6986 // Applies to at least: English PC-CD, English PC-Floppy
6987 // Responsible method (CD): sWireItShut::changeState
6988 // Responsible method (Floppy): sWireItShut::<noname144>
6989 // Fixes bug: #6458 (partly, see additional patch above)
6990 static const uint16 laurabow2SignatureRememberWiredEastDoor[] = {
6991 SIG_MAGICDWORD,
6992 0x33, 0x27, // jmp [ret]
6993 0x3c, // dup
6994 0x35, 0x06, // ldi 06
6995 0x1a, // eq?
6996 0x31, 0x21, // bnt [skip step]
6997 SIG_END
6998 };
6999
7000 static const uint16 laurabow2PatchRememberWiredEastDoor[] = {
7001 PATCH_ADDTOOFFSET(+2), // skip jmp [ret]
7002 0x34, PATCH_UINT16(0x0001), // ldi 0001
7003 0xa1, PATCH_UINT16(0x0061), // sag global[97d] (set our eastDoor-wired-global)
7004 PATCH_END
7005 };
7006
7007 // WORKAROUND: Script needed, because of differences in our pathfinding
7008 // algorithm
7009 // It's possible to walk through the closed door in room 448 and enter the crate
7010 // room before act 5 due to differences in our pathfinding algorithm from Sierra's.
7011 // This edge case appears to be due to one of the door's points being one pixel
7012 // within the room's walkable boundary, allowing ego to walk through this edge
7013 // from certain positions. We work around this by moving the door's point by
7014 // two pixels so that it's outside the room's walkable boundary.
7015 //
7016 // Applies to: All Floppy and CD versions
7017 // Responsible method: transomDoor:createPoly
7018 // Fixes bug: #9952
7019 static const uint16 laurabow2SignatureFixArmorHallDoorPathfinding[] = {
7020 SIG_MAGICDWORD,
7021 0x39, 0x50, // pushi 50 [ x = 80 ]
7022 0x39, 0x7d, // pushi 7d [ y = 125 ]
7023 SIG_END
7024 };
7025
7026 static const uint16 laurabow2PatchFixArmorHallDoorPathfinding[] = {
7027 PATCH_ADDTOOFFSET(+2),
7028 0x39, 0x7f, // pushi 7f [ y = 127 ]
7029 PATCH_END
7030 };
7031
7032 // The crate room (room 460) in act 5 locks up the game if you enter from the
7033 // elevator (room 660), swing the hanging crate, and then attempt to leave
7034 // back through the elevator door.
7035 //
7036 // The state of the wall crate that blocks the elevator door is tracked by
7037 // setting local[0] to 1 when you push it out of the way, but Sierra forgot
7038 // to reinitialize local[0] when you re-enter via the elevator door, causing
7039 // it to be out of sync with the room state. When you then swing the hanging
7040 // crate, sSwingIt:changeState(6) tests local[0] to see which polygon it
7041 // should set as the room's obstacle and incorrectly uses the one that blocks
7042 // both doors. Attempting to use the elevator door then locks up the game as
7043 // the obstacle polygon prevents ego from reaching the destination.
7044 //
7045 // Someone noticed that local[0] wasn't always initialized as
7046 // shoveCrate:doVerb(4) tests both local[0] and the previous room to see if it
7047 // was the elevator.
7048 //
7049 // We fix this by setting local[0] to 1 if the previous room was the elevator
7050 // during sSwingIt:changeState(3), just in time before it gets tested in
7051 // sSwingIt:changeState(6). Luckily for us, the handlers for states 3 and 4
7052 // don't do anything but load zero, making them two consecutive conditions
7053 // of no-ops. By merging them into a single condition for state 3 we have
7054 // a whopping 13 bytes available to add code to set local[0] correctly.
7055 //
7056 // Applies to: All Floppy and CD versions
7057 // Fixes bug: #10701
7058 static const uint16 laurabow2SignatureFixCrateRoomEastDoorLockup[] = {
7059 0x1a, // eq? [ state 3? ]
7060 SIG_MAGICDWORD,
7061 0x31, 0x05, // bnt [ state 4 ]
7062 0x35, 0x00, // ldi 0
7063 0x32, SIG_ADDTOOFFSET(+2), // jmp [ exit switch. floppy: b3, cd: bb ]
7064 0x3c, // dup
7065 0x35, 0x04, // ldi 4
7066 0x1a, // eq? [ state 4? ]
7067 0x31, 0x05, // bnt [ state 5 ]
7068 SIG_END
7069 };
7070
7071 static const uint16 laurabow2PatchFixCrateRoomEastDoorLockup[] = {
7072 PATCH_ADDTOOFFSET(+1), // eq? [ state 3? ]
7073 0x31, 0x10, // bnt [ state 5 ]
7074 0x89, 0x0c, // lsg global[0c] [ previous room # ]
7075 0x34, PATCH_UINT16(0x0294), // ldi 660d [ elevator room # ]
7076 0x1a, // eq?
7077 0x8b, 0x00, // lsl local[0]
7078 0x02, // add
7079 0xa3, 0x00, // sal local[0] [ local[0] += (global[0c] == 660d) ]
7080 PATCH_END
7081 };
7082
7083 // Ego can get stuck in the elevator (room 660) by walking to the lower left.
7084 // This also happens in Sierra's interpreter. We adjust the room's obstacle
7085 // polygon so that ego can't reach the problematic corner positions.
7086 // This is a heap patch for the coordinates used in poly2660a:points.
7087 //
7088 // Applies to: All Floppy and CD versions
7089 // Fixes bug: #10702
7090 static const uint16 laurabow2SignatureFixElevatorLockup[] = {
7091 SIG_MAGICDWORD,
7092 SIG_UINT16(0x008b), // x = 139d
7093 SIG_UINT16(0x0072), // y = 114d
7094 SIG_UINT16(0x007b), // x = 123d
7095 SIG_UINT16(0x008d), // y = 141d
7096 SIG_END
7097 };
7098
7099 static const uint16 laurabow2PatchFixElevatorLockup[] = {
7100 PATCH_UINT16(0x008f), // x = 143d
7101 PATCH_ADDTOOFFSET(+4),
7102 PATCH_UINT16(0x00aa), // y = 170d
7103 PATCH_END
7104 };
7105
7106 // The act 4 back rub scene in Yvette's (room 550) locks up the game when
7107 // entered from Carrington's (room 560) instead of the hallway (room 510).
7108 //
7109 // The difference is that entering from the hallway sets the room script to
7110 // eRS (Enter Room Script) and entering from Carrington's doesn't set any
7111 // room script. When sBackRubInterrupted moves ego off screen to the south,
7112 // lRS (Leave Room Script) is run by LBRoom:doit if a room script isn't set,
7113 // and lRS:changState(0) calls handsOff. Since control is already disabled,
7114 // this unexpected second handsOff causes handsOn(1) to restore the disabled
7115 // state in the hallway and the user never regains control.
7116 //
7117 // We fix this by setting sBackRubInterrupted as the room's script instead of
7118 // backRub's script in backRub:doVerb/<noname300>(0). The script executes the
7119 // same but having it set as the room script prevents LBRoom:doit from running
7120 // lRS which prevents the extra handsOff. This patch overwrites backRub's
7121 // default verb handler but that's okay because that code never executes.
7122 // doVerb is only called by sBackRubViewing:changeState(6) which passes verb 0.
7123 // The entire scene is in handsOff mode so the user can't send any verbs.
7124 //
7125 // Affects: All Floppy and CD versions
7126 // Responsible method: backRub:doVerb/<noname300> in script 550
7127 // Fixes bug: #10729
7128 static const uint16 laurabow2SignatureFixBackRubEastEntranceLockup[] = {
7129 SIG_MAGICDWORD,
7130 0x31, 0x0c, // bnt 0c [ unused default verb handler ]
7131 0x38, SIG_UINT16(0x0092), // pushi 0092 [ setScript/<noname146> ]
7132 0x78, // push1
7133 0x72, SIG_ADDTOOFFSET(+2), // lofsa sBackRubInterrupted [ cd: 0c94, floppy: 0c70 ]
7134 0x36, // push
7135 0x54, 0x06, // self 6 [ self:setScript sBackRubInterrupted ]
7136 0x33, 0x09, // jmp 9 [ exit switch ]
7137 0x38, SIG_ADDTOOFFSET(+2), // pushi doVerb/<noname300> [ cd: 011d, floppy: 012c ]
7138 SIG_END
7139 };
7140
7141 static const uint16 laurabow2PatchFixBackRubEastEntranceLockup[] = {
7142 PATCH_ADDTOOFFSET(+10),
7143 0x81, 0x02, // lag global[2] [ rm550 ]
7144 0x4a, 0x06, // send 6 [ rm550:setScript sBackRubInterrupted ]
7145 0x32, PATCH_UINT16(0x0006), // jmp 6 [ exit switch ]
7146 PATCH_END
7147 };
7148
7149 // The act 4 back rub scene in Yvette's (room 550) doesn't draw the typewriter,
7150 // desk lamp, and wastebasket when returning from Laura's close-up. These views
7151 // are added to the room pic and so they are disposed of when the pic changes.
7152 // This also occurs in Sierra's interpreter.
7153 //
7154 // We fix this by removing the addToPic call from these views so that they are
7155 // members of the cast like the other views in the room and survive pic change.
7156 //
7157 // Applies to: All Floppy and CD versions
7158 // Responsible method: rm550:init/<noname110>
7159 // Fixes bug #10894
7160 static const uint16 laurabow2SignatureFixDisappearingDeskItems[] = {
7161 SIG_MAGICDWORD,
7162 0x39, 0x64, // pushi 64
7163 0x39, 0x7e, // pushi 7e
7164 0x38, SIG_ADDTOOFFSET(+2), // pushi addToPic
7165 0x76, // push0
7166 SIG_ADDTOOFFSET(+3),
7167 0x4a, 0x20, // send 20 [ typewriter ... addToPic: ]
7168 SIG_ADDTOOFFSET(+26),
7169 0x38, SIG_ADDTOOFFSET(+2), // pushi addToPic
7170 0x76, // push0
7171 SIG_ADDTOOFFSET(+3),
7172 0x4a, 0x18, // send 18 [ deskLamp ... addToPic: ]
7173 SIG_ADDTOOFFSET(+27),
7174 0x38, SIG_ADDTOOFFSET(+2), // pushi addToPic
7175 0x76, // push0
7176 SIG_ADDTOOFFSET(+17),
7177 0x4a, 0x16, // send 16 [ wasteBasket ... addToPic: ... ]
7178 SIG_END
7179 };
7180
7181 static const uint16 laurabow2PatchFixDisappearingDeskItems[] = {
7182 PATCH_ADDTOOFFSET(+4),
7183 0x32, PATCH_UINT16(0x0001), // jmp 0001
7184 PATCH_ADDTOOFFSET(+4),
7185 0x4a, 0x1c, // send 1c [ init typewriter without addToPic ]
7186 PATCH_ADDTOOFFSET(+26),
7187 0x32, PATCH_UINT16(0x0001), // jmp 0001
7188 PATCH_ADDTOOFFSET(+4),
7189 0x4a, 0x14, // send 14 [ init deskLamp without addToPic ]
7190 PATCH_ADDTOOFFSET(+27),
7191 0x32, PATCH_UINT16(0x0001), // jmp 0001
7192 PATCH_ADDTOOFFSET(+18),
7193 0x4a, 0x12, // send 12 [ init wasteBasket without addToPic ]
7194 PATCH_END
7195 };
7196
7197 // LB2 Floppy 1.0 doesn't initialize act 4 correctly when triggered by finding
7198 // the dagger, causing the act 4 scene in Yvette's (room 550) to lockup.
7199 //
7200 // The Yvette/Olympia/Steve scene in act 4 (rooms 550 and 510) expects
7201 // global[111] to be 11. This global tracks Yvette's state throughout acts 3
7202 // and 4, incrementing as you listen to her conversations and witness her
7203 // scenes. Some of these are optional, so at the end of act 3 it can be less
7204 // than 11. rm510:init initializes global[111] to 11 when act 4 is triggered
7205 // by reporting Ernie's death, but no such initialization occurs when act 4 is
7206 // triggered by finding the dagger (rooms 610 and 620). What happens when the
7207 // global isn't 11 depends on its value. Some values, such as 8, cause the act
7208 // 4 scene to never complete and never restore control to the user.
7209 //
7210 // We fix this the way Sierra did in floppy 1.1 and cd versions by setting
7211 // global[111] to 11 in actBreak:init when act 4 starts so that it's always
7212 // initialized.
7213 //
7214 // Applies to: Floppy 1.0 English only
7215 // Responsible method: actBreak:<noname150> which is really init
7216 // Fixes bug: #10716
7217 static const uint16 laurabow2SignatureFixAct4Initialization[] = {
7218 SIG_MAGICDWORD,
7219 0xa3, 0x08, // sal local[8] [ 1.0 floppy only ]
7220 0x89, 0x0c, // lsg global[0c] [ previous room ]
7221 0x34, SIG_UINT16(0x026c), // ldi 026c [ room 620 ]
7222 0x1a, // eq?
7223 0x31, 0x05, // bnt 5
7224 0x34, SIG_UINT16(0x0262), // ldi 0262 [ room 610 ]
7225 0x33, 0x03, // jmp 3
7226 0x34, SIG_UINT16(0x01fe), // ldi 01fe [ room 510 ]
7227 0xa3, 0x00, // sal local[0] [ (previous room == 620) ? 610 : 510 ]
7228 0x33, 0x2d, // jmp 2d [ exit switch ]
7229 SIG_END
7230 };
7231
7232 static const uint16 laurabow2PatchFixAct4Initialization[] = {
7233 PATCH_ADDTOOFFSET(+2),
7234 0x35, 0x0b, // ldi 0b
7235 0xa1, 0x6f, // sag global[6f] [ global[111d] = 11 ]
7236 0x89, 0x0c, // lsg global[0c] [ previous room ]
7237 0x34, PATCH_UINT16(0x026c), // ldi 026c [ room 620 ]
7238 0x1a, // eq?
7239 0x39, 0x64, // pushi 64
7240 0x06, // mul
7241 0x38, PATCH_UINT16(0x01fe), // pushi 01fe
7242 0x02, // add [ acc = ((previous room == 620) * 100) + 510 ]
7243 0x32, PATCH_UINT16(0x0013), // jmp 0013 [ jmp to: sal 0, jmp exit switch ]
7244 PATCH_END
7245 };
7246
7247 // The armor exhibit rooms (440 and 448) have event handlers that fail to handle
7248 // all events, preventing messages from being displayed.
7249 //
7250 // Both armor rooms implement handleEvent to handle joystick events in certain
7251 // situations, but they only pass move events on to super:handleEvent, blocking
7252 // all other event types. Clicking on either room does nothing even though both
7253 // have messages to respond with. This also prevents messages when clicking on
7254 // Pippin Carter's armor inset. Sierra fixed the armor problem after the first
7255 // floppy release by adding code to detect and handle the inset's events,
7256 // instead of handling all events, leaving the room messages broken.
7257 //
7258 // We fix this by handling verb events in both rooms. This fixes room messages
7259 // in all versions and fixes armor inset messages in English floppy 1.0.
7260 //
7261 // Applies to: All Floppy and CD versions
7262 // Responsible methods: rm440:handleEvent/<noname133>, rm448:handleEvent/<noname133>
7263 // Fixes bugs #10709, #10895
7264 static const uint16 laurabow2SignatureHandleArmorRoomEvents[] = {
7265 0x87, 0x01, // lap 01
7266 0x4a, 0x04, // send 04 [ event type? ]
7267 SIG_MAGICDWORD,
7268 0x36, // push
7269 0x34, SIG_UINT16(0x1000), // ldi 1000 [ move event ]
7270 0x12, // and
7271 0x31, // bnt [ don't handle event ]
7272 SIG_END
7273 };
7274
7275 static const uint16 laurabow2PatchHandleArmorRoomEvents[] = {
7276 PATCH_ADDTOOFFSET(+5),
7277 0x34, PATCH_UINT16(0x5000), // ldi 5000 [ move event | verb event ]
7278 PATCH_END
7279 };
7280
7281 // The "bugs with meat" in the basement hallway (room 600) can lockup the game
7282 // if they appear while ego is leaving the room through one of the doors.
7283 //
7284 // bugsWithMeat cues after 5 seconds in the room and runs sDoMeat if no room
7285 // script is set. sDoMeat:changeState(0) calls handsOff. Ego might already be
7286 // leaving through the north door in handsOff mode, which is managed by lRS
7287 // (Leave Room Script), which doesn't prevent sDoMeat from running because lRS
7288 // isn't set as the room script. If the door is animating when the timer goes
7289 // off then ego will continue to Wolfe's (room 650) and the unexpected second
7290 // handsOff will cause handsOn(1) to restore the disabled state and the user
7291 // will never regain control. If sDoMeat runs after the door animates then
7292 // ego's movement will be interrupted and the door will be left open and broken.
7293 // Similar problems occur with the other door in the room.
7294 //
7295 // We fix this by patching bugsWithMeat:cue from testing if the room has no
7296 // script to instead testing if the user has control before running sDoMeat.
7297 // All of the room's scripts call handsOff in state 0 and handsOn in their
7298 // final state so this change just extends the interruption test to include
7299 // other handsOff scripts.
7300 //
7301 // The signature and patch are duplicated for floppy and cd versions due to
7302 // User:canControl having different selector values between versions, floppy
7303 // versions not including selector names, and User:canControl's selector
7304 // values not appearing in the script being patched.
7305 //
7306 // Applies to: All Floppy and CD versions
7307 // Responsible method: bugsWithMeat:cue/<noname145>
7308 // Fixes bug: #10730
7309 static const uint16 laurabow2FloppySignatureFixBugsWithMeat[] = {
7310 SIG_MAGICDWORD,
7311 0x57, 0x32, 0x06, // super Actor[32], 6 [ floppy: 32, cd: 31 ]
7312 0x3a, // toss
7313 0x48, // ret [ end of bugsWithMeat:<noname300> aka doVerb ]
7314 0x38, SIG_UINT16(0x008e), // pushi 008e [ <noname142> aka script ]
7315 0x76, // push0
7316 0x81, 0x02, // lag global[2] [ rm600 ]
7317 0x4a, 0x04, // send 4
7318 0x31, 0x0e, // bnt 0e [ run sDoMeat if not rm600:<noname142>? ]
7319 SIG_END
7320 };
7321
7322 static const uint16 laurabow2FloppyPatchFixBugsWithMeat[] = {
7323 PATCH_ADDTOOFFSET(+5),
7324 0x38, PATCH_UINT16(0x00ed), // pushi 00ed [ <noname237> aka canControl ]
7325 0x76, // push0
7326 0x81, 0x50, // lag global[50] [ User ]
7327 0x4a, 0x04, // send 4
7328 0x2f, 0x0e, // bt 0e [ run sDoMeat if User:<noname237>? ]
7329 PATCH_END
7330 };
7331
7332 // cd version of the above signature/patch
7333 static const uint16 laurabow2CDSignatureFixBugsWithMeat[] = {
7334 SIG_MAGICDWORD,
7335 0x57, 0x31, 0x06, // super Actor[31], 6 [ floppy: 32, cd: 31 ]
7336 0x3a, // toss
7337 0x48, // ret [ end of bugsWithMeat:doVerb ]
7338 0x38, SIG_UINT16(0x008e), // pushi 008e [ script ]
7339 0x76, // push0
7340 0x81, 0x02, // lag global[2] [ rm600 ]
7341 0x4a, 0x04, // send 4
7342 0x31, 0x0e, // bnt 0e [ run sDoMeat if not rm600:script? ]
7343 SIG_END
7344 };
7345
7346 static const uint16 laurabow2CDPatchFixBugsWithMeat[] = {
7347 PATCH_ADDTOOFFSET(+5),
7348 0x38, PATCH_UINT16(0x00f6), // pushi 00f6 [ canControl ]
7349 0x76, // push0
7350 0x81, 0x50, // lag global[50] [ User ]
7351 0x4a, 0x04, // send 4
7352 0x2f, 0x0e, // bt 0e [ run sDoMeat if User:canControl? ]
7353 PATCH_END
7354 };
7355
7356 // LB2 CD ends act 5 in the middle of the finale music instead of waiting for
7357 // it to complete. This is a script bug and occurs in Sierra's interpreter.
7358 //
7359 // When catching the killer in room 480, sWrapMusic is used to play the chomp
7360 // sound followed by the finale music. sWrapMusic uses localSound to play both
7361 // resources. sOrileyCaught:doit waits for the music to complete by testing
7362 // localSound:prevSignal for -1 before proceeding to act 6. This worked in
7363 // floppy versions.
7364 //
7365 // The problem is that CD versions include a newer Sound class with different
7366 // behavior. This new Sound:play doesn't call Sound:init on subsequent plays.
7367 // Sound:init is what initializes prevSignal to 0, and so localSound:prevSignal
7368 // is no longer re-initialized from -1 to 0 after playing the chomp, causing
7369 // sOrileyCaught:doit to treat the music as having immediately completed.
7370 //
7371 // We fix this by changing sOrileyCaught:doit to instead test localSound:handle
7372 // to determine if the music has completed. Sound:handle is always set when
7373 // playing and cleared when stopped or disposed.
7374 //
7375 // Applies to: All CD versions
7376 // Responsible method: sOrileyCaught:doit
7377 // Fixes bug: #10808
7378 static const uint16 laurabow2CDSignatureFixAct5FinaleMusic[] = {
7379 SIG_MAGICDWORD,
7380 0x38, SIG_UINT16(0x00ab), // pushi 00ab [ prevSignal ]
7381 0x76, // push0
7382 0x72, SIG_UINT16(0x083a), // lofsa localSound
7383 0x4a, 0x04, // send 4
7384 0x36, // push
7385 0x35, 0xff, // ldi ff
7386 SIG_END
7387 };
7388
7389 static const uint16 laurabow2CDPatchFixAct5FinaleMusic[] = {
7390 0x38, PATCH_UINT16(0x005a), // pushi 005a [ handle ]
7391 PATCH_ADDTOOFFSET(+7),
7392 0x35, 0x00, // ldi 00
7393 PATCH_END
7394 };
7395
7396 // LB2 does a speed test during startup (room 28) to determine the initial
7397 // detail level and to use for pacing later scenes. Even with the highest
7398 // score the detail level is only set to medium instead of highest like
7399 // other SCI games.
7400 //
7401 // Platforms such as iOS can introduce a lag during game initialization that
7402 // causes the speed test to occasionally get the lowest score, causing
7403 // detail to be initialized to lowest and subsequent scenes such as the act 5
7404 // chase scene to go very slow.
7405 //
7406 // We patch startGame:doit to ignore the score and always set the highest detail
7407 // level and cpu speed so that detail needn't be manually increased and act 5
7408 // behaves consistently. This also helps touchscreen devices as the game's
7409 // detail slider is prohibitively difficult to manually set to highest without
7410 // switching from the default touch mode.
7411 //
7412 // Applies to: All Floppy and CD versions
7413 // Responsible method: startGame:doit/<noname57>
7414 // Fixes bug: #10761
7415 static const uint16 laurabow2SignatureDisableSpeedTest[] = {
7416 0x89, 0x57, // lsg global[57] [ speed test result ]
7417 SIG_MAGICDWORD,
7418 0x35, 0x03, // ldi 03 [ low-speed threshold ]
7419 0x24, // le?
7420 0x31, 0x04, // bnt 04
7421 0x35, 0x01, // ldi 01 [ lowest detail ]
7422 0x33, 0x0d, // jmp 0d
7423 0x89, 0x57, // lsg global[57] [ speed test result ]
7424 SIG_END
7425 };
7426
7427 static const uint16 laurabow2PatchDisableSpeedTest[] = {
7428 0x38, PATCH_UINT16(0x0005), // pushi 0005
7429 0x81, 0x01, // lag global[1]
7430 0x4a, 0x06, // send 06 [ LB2:detailLevel = 5, max detail ]
7431 0x35, 0x0f, // ldi 0f
7432 0xa1, 0x57, // sag global[57] [ global[57] = f, max cpu speed ]
7433 0x33, 0x10, // jmp 10 [ continue init ]
7434 PATCH_END
7435 };
7436
7437 // LB2CD reduces the music volume significantly during the introduction when
7438 // characters talk while disembarking the ship in room 120. This is done so
7439 // that their speech can be heard but it also occurs in text-only mode.
7440 //
7441 // Interestingly, this is the only script that manually reduces volume during
7442 // speech, as it's a workaround for bugs in the Narrator and Talker scripts.
7443 // They're supposed to automatically reduce music volume, but this scheme fails
7444 // when multiple appear at once, which seems to only occur in the introduction.
7445 //
7446 // We patch the introduction to skip the volume reduction when in text-only mode
7447 // so that the music doesn't abruptly go away and come back.
7448 //
7449 // Applies to: All CD versions
7450 // Responsible method: sDisembark:changeState
7451 // Fixes bug #10916
7452 static const uint16 laurabow2CDSignatureIntroVolumeChange[] = {
7453 0x31, 0x2a, // bnt 2a [ state 3 ]
7454 SIG_ADDTOOFFSET(+2),
7455 0x31, 0x1f, // bnt 1f
7456 SIG_ADDTOOFFSET(+28),
7457 0x32, SIG_UINT16(0x00f7), // jmp 00f7 [ end of method ]
7458 0x35, 0x02, // ldi 02
7459 0x65, 0x1a, // aTop cycles [ cycles = 2 ]
7460 0x32, SIG_UINT16(0x00f0), // jmp 00f0 [ end of method ]
7461 0x3c, // dup
7462 0x35, 0x03, // ldi 03
7463 0x1a, // eq?
7464 0x31, 0x25, // bnt 25 [ state 4 ]
7465 0x38, SIG_SELECTOR16(setVol), // pushi setVol
7466 0x78, // push1
7467 SIG_MAGICDWORD,
7468 0x39, 0x28, // pushi 28
7469 0x81, 0x66, // lag 66
7470 0x4a, 0x06, // send 06 [ gameMusic1 setVol: 40 ]
7471 SIG_END
7472 };
7473
7474 static const uint16 laurabow2CDPatchIntroVolumeChange[] = {
7475 0x31, 0x25, // bnt 25 [ state 3 ]
7476 PATCH_ADDTOOFFSET(+2),
7477 0x31, 0x1e, // bnt 1e
7478 PATCH_ADDTOOFFSET(+28),
7479 0x3a, // toss
7480 0x48, // ret
7481 0x7a, // push2
7482 0x69, 0x1a, // sTop cycles [ cycles = 2 ]
7483 0x3c, // dup
7484 0x35, 0x03, // ldi 03
7485 0x1a, // eq?
7486 0x31, 0x2a, // bnt 2a [ state 4 ]
7487 0x89, 0x5a, // lsg 5a [ message mode ]
7488 0x1a, // eq? [ is text-only mode? ]
7489 0x2f, 0x0a, // bt 0a [ skip volume reduction ]
7490 PATCH_END
7491 };
7492
7493 // Laura Bow 2 CD resets the audio mode to speech on init/restart
7494 // We already sync the settings from ScummVM (see SciEngine::syncIngameAudioOptions())
7495 // and this script code would make it impossible to see the intro using "dual" mode w/o using debugger command
7496 // That's why we remove the corresponding code
7497 // Patched method: LB2::init, rm100::init
7498 static const uint16 laurabow2CDSignatureAudioTextSupportModeReset[] = {
7499 SIG_MAGICDWORD,
7500 0x35, 0x02, // ldi 02
7501 0xa1, 0x5a, // sag global[5a]
7502 SIG_END
7503 };
7504
7505 static const uint16 laurabow2CDPatchAudioTextSupportModeReset[] = {
7506 0x34, PATCH_UINT16(0x0001), // ldi 0001 (waste bytes)
7507 0x18, // not (waste bytes)
7508 PATCH_END
7509 };
7510
7511 // Directly use global[5a] for view-cel id
7512 // That way it's possible to use a new "dual" mode view in the game menu
7513 // View 995, loop 13, cel 0 -> "text"
7514 // View 995, loop 13, cel 1 -> "speech"
7515 // View 995, loop 13, cel 2 -> "dual" (this view is injected by us into the game)
7516 // Patched method: gcWin::open
7517 static const uint16 laurabow2CDSignatureAudioTextMenuSupport1[] = {
7518 SIG_MAGICDWORD,
7519 0x89, 0x5a, // lsg global[5a]
7520 0x35, 0x02, // ldi 02
7521 0x1a, // eq?
7522 0x36, // push
7523 SIG_END
7524 };
7525
7526 static const uint16 laurabow2CDPatchAudioTextMenuSupport1[] = {
7527 PATCH_ADDTOOFFSET(+2),
7528 0x35, 0x01, // ldi 01
7529 0x04, // sub
7530 PATCH_END
7531 };
7532
7533 // Adds another button state for the text/audio button. We currently use the "speech" view for "dual" mode.
7534 // Patched method: iconMode::doit
7535 static const uint16 laurabow2CDSignatureAudioTextMenuSupport2[] = {
7536 SIG_MAGICDWORD,
7537 0x89, 0x5a, // lsg global[5a]
7538 0x3c, // dup
7539 0x1a, // eq?
7540 0x31, 0x0a, // bnt [set text mode]
7541 0x35, 0x02, // ldi 02
7542 0xa1, 0x5a, // sag global[5a]
7543 0x35, 0x01, // ldi 01
7544 0xa5, 0x00, // sat temp[0]
7545 0x33, 0x0e, // jmp [draw cel code]
7546 0x3c, // dup
7547 0x35, 0x02, // ldi 02
7548 0x1a, // eq?
7549 0x31, 0x08, // bnt [draw cel code]
7550 0x35, 0x01, // ldi 01
7551 0xa1, 0x5a, // sag global[5a]
7552 0x35, 0x00, // ldi 00
7553 0xa5, 0x00, // sat temp[0]
7554 0x3a, // toss
7555 SIG_END
7556 };
7557
7558 static const uint16 laurabow2CDPatchAudioTextMenuSupport2[] = {
7559 0x81, 0x5a, // lag global[5a]
7560 0x78, // push1
7561 0x02, // add
7562 0xa1, 0x5a, // sag global[5a]
7563 0x36, // push
7564 0x35, 0x03, // ldi 03
7565 0x1e, // gt?
7566 0x31, 0x03, // bnt [skip over]
7567 0x78, // push1
7568 0xa9, 0x5a, // ssg global[5a]
7569 0x89, 0x5a, // lsg global[5a]
7570 0x35, 0x01, // ldi 01
7571 0x04, // sub
7572 0xa5, 0x00, // sat temp[0] [ calculate global[5a] - 1 to use as view cel id ]
7573 0x33, 0x07, // jmp [draw cel code, don't do toss]
7574 PATCH_END
7575 };
7576
7577 // script, description, signature patch
7578 static const SciScriptPatcherEntry laurabow2Signatures[] = {
7579 { true, 560, "CD: painting closing immediately", 1, laurabow2CDSignaturePaintingClosing, laurabow2CDPatchPaintingClosing },
7580 { true, 0, "CD: fix problematic icon bar", 1, laurabow2CDSignatureFixProblematicIconBar, laurabow2CDPatchFixProblematicIconBar },
7581 { true, 90, "CD: fix yvette's tut response", 1, laurabow2CDSignatureFixYvetteTutResponse, laurabow2CDPatchFixYvetteTutResponse },
7582 { true, 350, "CD/Floppy: museum party fix entering south 1/2", 1, laurabow2SignatureMuseumPartyFixEnteringSouth1, laurabow2PatchMuseumPartyFixEnteringSouth1 },
7583 { true, 350, "CD/Floppy: museum party fix entering south 2/2", 1, laurabow2SignatureMuseumPartyFixEnteringSouth2, laurabow2PatchMuseumPartyFixEnteringSouth2 },
7584 { true, 430, "CD/Floppy: make wired east door persistent", 1, laurabow2SignatureRememberWiredEastDoor, laurabow2PatchRememberWiredEastDoor },
7585 { true, 430, "CD/Floppy: fix wired east door", 1, laurabow2SignatureFixWiredEastDoor, laurabow2PatchFixWiredEastDoor },
7586 { true, 448, "CD/Floppy: fix armor hall door pathfinding", 1, laurabow2SignatureFixArmorHallDoorPathfinding, laurabow2PatchFixArmorHallDoorPathfinding },
7587 { true, 460, "CD/Floppy: fix crate room east door lockup", 1, laurabow2SignatureFixCrateRoomEastDoorLockup, laurabow2PatchFixCrateRoomEastDoorLockup },
7588 { true, 2660, "CD/Floppy: fix elevator lockup", 1, laurabow2SignatureFixElevatorLockup, laurabow2PatchFixElevatorLockup },
7589 { true, 550, "CD/Floppy: fix back rub east entrance lockup", 1, laurabow2SignatureFixBackRubEastEntranceLockup, laurabow2PatchFixBackRubEastEntranceLockup },
7590 { true, 550, "CD/Floppy: fix disappearing desk items", 1, laurabow2SignatureFixDisappearingDeskItems, laurabow2PatchFixDisappearingDeskItems },
7591 { true, 26, "Floppy: fix act 4 initialization", 1, laurabow2SignatureFixAct4Initialization, laurabow2PatchFixAct4Initialization },
7592 { true, 440, "CD/Floppy: handle armor room events", 1, laurabow2SignatureHandleArmorRoomEvents, laurabow2PatchHandleArmorRoomEvents },
7593 { true, 448, "CD/Floppy: handle armor hall room events", 1, laurabow2SignatureHandleArmorRoomEvents, laurabow2PatchHandleArmorRoomEvents },
7594 { true, 600, "Floppy: fix bugs with meat", 1, laurabow2FloppySignatureFixBugsWithMeat, laurabow2FloppyPatchFixBugsWithMeat },
7595 { true, 600, "CD: fix bugs with meat", 1, laurabow2CDSignatureFixBugsWithMeat, laurabow2CDPatchFixBugsWithMeat },
7596 { true, 480, "CD: fix act 5 finale music", 1, laurabow2CDSignatureFixAct5FinaleMusic, laurabow2CDPatchFixAct5FinaleMusic },
7597 { true, 28, "CD/Floppy: disable speed test", 1, laurabow2SignatureDisableSpeedTest, laurabow2PatchDisableSpeedTest },
7598 { true, 120, "CD: disable intro volume change in text mode", 1, laurabow2CDSignatureIntroVolumeChange, laurabow2CDPatchIntroVolumeChange },
7599 // King's Quest 6 and Laura Bow 2 share basic patches for audio + text support
7600 { false, 924, "CD: audio + text support 1", 1, kq6laurabow2CDSignatureAudioTextSupport1, kq6laurabow2CDPatchAudioTextSupport1 },
7601 { false, 924, "CD: audio + text support 2", 1, kq6laurabow2CDSignatureAudioTextSupport2, kq6laurabow2CDPatchAudioTextSupport2 },
7602 { false, 924, "CD: audio + text support 3", 1, kq6laurabow2CDSignatureAudioTextSupport3, kq6laurabow2CDPatchAudioTextSupport3 },
7603 { false, 928, "CD: audio + text support 4", 1, kq6laurabow2CDSignatureAudioTextSupport4, kq6laurabow2CDPatchAudioTextSupport4 },
7604 { false, 928, "CD: audio + text support 5", 2, kq6laurabow2CDSignatureAudioTextSupport5, kq6laurabow2CDPatchAudioTextSupport5 },
7605 { false, 0, "CD: audio + text support disable mode reset", 1, laurabow2CDSignatureAudioTextSupportModeReset, laurabow2CDPatchAudioTextSupportModeReset },
7606 { false, 100, "CD: audio + text support disable mode reset", 1, laurabow2CDSignatureAudioTextSupportModeReset, laurabow2CDPatchAudioTextSupportModeReset },
7607 { false, 24, "CD: audio + text support LB2 menu 1", 1, laurabow2CDSignatureAudioTextMenuSupport1, laurabow2CDPatchAudioTextMenuSupport1 },
7608 { false, 24, "CD: audio + text support LB2 menu 2", 1, laurabow2CDSignatureAudioTextMenuSupport2, laurabow2CDPatchAudioTextMenuSupport2 },
7609 SCI_SIGNATUREENTRY_TERMINATOR
7610 };
7611
7612 // ===========================================================================
7613 // Mother Goose SCI1/SCI1.1
7614 // MG::replay somewhat calculates the savedgame-id used when saving again
7615 // this doesn't work right and we remove the code completely.
7616 // We set the savedgame-id directly right after restoring in kRestoreGame.
7617 // We also draw the background picture in here instead.
7618 // This Mixed Up Mother Goose draws the background picture before restoring,
7619 // instead of doing it properly in MG::replay. This fixes graphic issues,
7620 // when restoring from GMM.
7621 //
7622 // Applies to at least: English SCI1 CD, English SCI1.1 floppy, Japanese FM-Towns
7623 // Responsible method: MG::replay (script 0)
7624 static const uint16 mothergoose256SignatureReplay[] = {
7625 0x7a, // push2
7626 0x78, // push1
7627 0x5b, 0x00, 0xbe, // lea global[BEh]
7628 0x36, // push
7629 0x43, 0x70, 0x04, // callk MemorySegment
7630 0x7a, // push2
7631 0x5b, 0x00, 0xbe, // lea global[BEh]
7632 0x36, // push
7633 0x76, // push0
7634 0x43, 0x62, 0x04, // callk StrAt
7635 0xa1, 0xaa, // sag global[AAh]
7636 0x7a, // push2
7637 0x5b, 0x00, 0xbe, // lea global[BEh]
7638 0x36, // push
7639 0x78, // push1
7640 0x43, 0x62, 0x04, // callk StrAt
7641 0x36, // push
7642 0x35, 0x20, // ldi 20
7643 0x04, // sub
7644 0xa1, SIG_ADDTOOFFSET(+1), // sag global[57h], for FM-Towns: sag global[9Dh]
7645 // 35 bytes
7646 0x39, 0x03, // pushi 03
7647 0x89, SIG_ADDTOOFFSET(+1), // lsg global[1Dh], for FM-Towns: lsg global[1Eh]
7648 0x76, // push0
7649 0x7a, // push2
7650 0x5b, 0x00, 0xbe, // lea global[BEh]
7651 0x36, // push
7652 0x7a, // push2
7653 0x43, 0x62, 0x04, // callk StrAt
7654 0x36, // push
7655 0x35, 0x01, // ldi 01
7656 0x04, // sub
7657 0x36, // push
7658 0x43, 0x62, 0x06, // callk StrAt
7659 // 22 bytes
7660 0x7a, // push2
7661 0x5b, 0x00, 0xbe, // lea global[BEh]
7662 0x36, // push
7663 0x39, 0x03, // pushi 03
7664 0x43, 0x62, 0x04, // callk StrAt
7665 // 10 bytes
7666 0x36, // push
7667 0x35, SIG_MAGICDWORD, 0x20, // ldi 20
7668 0x04, // sub
7669 0xa1, 0xb3, // sag global[B3h]
7670 // 6 bytes
7671 SIG_END
7672 };
7673
7674 static const uint16 mothergoose256PatchReplay[] = {
7675 0x39, 0x06, // pushi 06
7676 0x76, // push0
7677 0x76, // push0
7678 0x38, PATCH_UINT16(200), // pushi 200d
7679 0x38, PATCH_UINT16(320), // pushi 320d
7680 0x76, // push0
7681 0x76, // push0
7682 0x43, 0x15, 0x0c, // callk SetPort -> set picture port to full screen
7683 // 15 bytes
7684 0x39, 0x04, // pushi 04
7685 0x3c, // dup
7686 0x76, // push0
7687 0x38, PATCH_UINT16(255), // pushi 255d
7688 0x76, // push0
7689 0x43, 0x6f, 0x08, // callk Palette -> set intensity to 0 for all colors
7690 // 11 bytes
7691 0x7a, // push2
7692 0x38, PATCH_UINT16(800), // pushi 800
7693 0x76, // push0
7694 0x43, 0x08, 0x04, // callk DrawPic -> draw picture 800
7695 // 8 bytes
7696 0x39, 0x06, // pushi 06
7697 0x39, 0x0c, // pushi 0Ch
7698 0x76, // push0
7699 0x76, // push0
7700 0x38, PATCH_UINT16(200), // pushi 200
7701 0x38, PATCH_UINT16(320), // pushi 320
7702 0x78, // push1
7703 0x43, 0x6c, 0x0c, // callk Graph -> send everything to screen
7704 // 16 bytes
7705 0x39, 0x06, // pushi 06
7706 0x76, // push0
7707 0x76, // push0
7708 0x38, PATCH_UINT16(156), // pushi 156d
7709 0x38, PATCH_UINT16(258), // pushi 258d
7710 0x39, 0x03, // pushi 03
7711 0x39, 0x04, // pushi 04
7712 0x43, 0x15, 0x0c, // callk SetPort -> set picture port back
7713 // 17 bytes
7714 0x34, PATCH_UINT16(0x0000), // ldi 0000 (dummy)
7715 0x34, PATCH_UINT16(0x0000), // ldi 0000 (dummy)
7716 PATCH_END
7717 };
7718
7719 // when saving, it also checks if the savegame ID is below 13.
7720 // we change this to check if below 113 instead
7721 //
7722 // Applies to at least: English SCI1 CD, English SCI1.1 floppy, Japanese FM-Towns
7723 // Responsible method: Game::save (script 994 for SCI1), MG::save (script 0 for SCI1.1)
7724 static const uint16 mothergoose256SignatureSaveLimit[] = {
7725 0x89, SIG_MAGICDWORD, 0xb3, // lsg global[b3]
7726 0x35, 0x0d, // ldi 0d
7727 0x20, // ge?
7728 SIG_END
7729 };
7730
7731 static const uint16 mothergoose256PatchSaveLimit[] = {
7732 PATCH_ADDTOOFFSET(+2),
7733 0x35, 0x0d + SAVEGAMEID_OFFICIALRANGE_START, // ldi 113d
7734 PATCH_END
7735 };
7736
7737 // Clicking a button on the main menu as the screen fades in causes a message to
7738 // be sent to a non-object when coming from the Sierra logo. This also crashes
7739 // the original. If returning from a menu then the previous button is clicked.
7740 //
7741 // After the main menu screen fades in, IconBar:doit polls for events in a loop
7742 // until a mouse click or key press occurs over a button. choices:show then
7743 // calls highlightedIcon:message to run the correct action. IconBar:doit
7744 // updates this property on each iteration unless the event is a mouse click.
7745 // If a button is clicked before polling begins then IconBar:doit exits on its
7746 // first iteration without setting highlightedIcon. This property is never
7747 // reset and its stale value can get reused when returning from a menu.
7748 //
7749 // These problems would be simple to fix in the event polling loop, but that
7750 // code is in the IconBar and GameControls classes that affect each screen.
7751 // Instead we surround the loop with fixes. The event queue is now drained of
7752 // mouse clicks and highlightedIcon is cleared before polling. Afterwards,
7753 // highlightedIcon is verified and the loop is repeated if it's not set. This
7754 // discards clicks during fade-in, resets state when canceling a subsequent
7755 // menu, and prevents timing edge cases from crashing should a click ever
7756 // register on the loop's first iteration. We make room for this by overwriting
7757 // the code that initializes the cursor when kHaveMouse reports no mouse, as
7758 // that doesn't apply to ScummVM. The CD version's menu was rewritten and
7759 // doesn't have these problems.
7760 //
7761 // Applies to: English PC Floppy
7762 // Responsible method: choices:show
7763 // Fixes bug #9681
7764 static const uint16 mothergoose256SignatureMainMenuCrash[] = {
7765 0x43, SIG_MAGICDWORD, 0x27, 0x00, // callk HaveMouse
7766 0x31, 0x0e, // bnt 0e [ no mouse ]
7767 SIG_ADDTOOFFSET(+0x0b),
7768 0x32, SIG_UINT16(0x0036), // jmp 0036 [ skip no-mouse code ]
7769 SIG_ADDTOOFFSET(+0x1a),
7770 // no mouse
7771 0x76, // push0
7772 0x63, 0x1e, // pToa curIcon
7773 0x4a, 0x04, // send 04
7774 0x04, // sub
7775 0x36, // push
7776 0x35, 0x02, // ldi 02
7777 0x08, // div
7778 0x02, // add
7779 0x36, // push
7780 0x39, 0x08, // pushi 08
7781 0x76, // push0
7782 0x63, 0x1e, // pToa curIcon
7783 0x4a, 0x04, // send 04
7784 0x36, // push
7785 0x35, 0x03, // ldi 03
7786 0x04, // sub
7787 // event polling loop
7788 0x36, // push
7789 0x81, 0x01, // lag 01
7790 0x4a, 0x0c, // send 0c
7791 0x39, SIG_SELECTOR8(doit), // pushi doit
7792 0x76, // push0
7793 0x39, SIG_SELECTOR8(hide), // pushi hide
7794 0x76, // push0
7795 0x54, 0x08, // self 08 [ self doit: hide: ]
7796 SIG_END
7797 };
7798
7799 static const uint16 mothergoose256PatchMainMenuCrash[] = {
7800 PATCH_ADDTOOFFSET(+3),
7801 0x33, 0x00, // jmp 00 [ always run mouse code ]
7802 PATCH_ADDTOOFFSET(+0x0b),
7803 0x32, PATCH_UINT16(0x001a), // jmp 001a [ skip no-mouse code ]
7804 PATCH_ADDTOOFFSET(+0x1a),
7805 0x76, // push0
7806 0x69, 0x20, // sTop highlightedIcon [ highlightedIcon = 0 ]
7807 0x39, PATCH_SELECTOR8(new), // pushi new
7808 0x78, // push1
7809 0x39, 0x03, // pushi 03 [ mouse down/up ]
7810 0x51, 0x07, // class Event
7811 0x4a, 0x06, // send 06 [ Event new: 3 ]
7812 0x39, PATCH_SELECTOR8(dispose), // pushi dispose
7813 0x76, // push0
7814 0x39, PATCH_SELECTOR8(type), // pushi type
7815 0x76, // push0
7816 0x4a, 0x08, // send 08 [ event dispose: type? ]
7817 0x2f, 0xed, // bt ed [ loop until no more mouse down/up events ]
7818 0x39, PATCH_SELECTOR8(doit), // pushi doit
7819 0x76, // push0
7820 0x54, 0x04, // self 04 [ self doit: ]
7821 0x63, 0x20, // pToa highlightedIcon
7822 0x31, 0xf7, // bnt f7 [ repeat event loop until highlightedIcon is set ]
7823 PATCH_ADDTOOFFSET(+3),
7824 0x54, 0x04, // self 04 [ self hide: ]
7825 PATCH_END
7826 };
7827
7828 // script, description, signature patch
7829 static const SciScriptPatcherEntry mothergoose256Signatures[] = {
7830 { true, 0, "replay save issue", 1, mothergoose256SignatureReplay, mothergoose256PatchReplay },
7831 { true, 0, "save limit dialog (SCI1.1)", 1, mothergoose256SignatureSaveLimit, mothergoose256PatchSaveLimit },
7832 { true, 994, "save limit dialog (SCI1)", 1, mothergoose256SignatureSaveLimit, mothergoose256PatchSaveLimit },
7833 { true, 90, "main menu button crash", 1, mothergoose256SignatureMainMenuCrash, mothergoose256PatchMainMenuCrash },
7834 SCI_SIGNATUREENTRY_TERMINATOR
7835 };
7836
7837 #ifdef ENABLE_SCI32
7838 #pragma mark -
7839 #pragma mark Mixed-up Mother Goose Deluxe
7840
7841 // The game uses pic 10005 to render the Sierra logo, but then it also
7842 // initialises a logo object with view 502 on the same priority as the pic. In
7843 // the original interpreter, it is dumb luck which is drawn first (based on the
7844 // order of the memory IDs), though usually the pic is drawn first because not
7845 // many objects have been created at the start of the game. In ScummVM, the
7846 // renderer guarantees a sort order based on the creation order of screen items,
7847 // and since the view is created after the pic, it wins and is drawn on top.
7848 // This patch stops the view object from being created at all.
7849 //
7850 // Applies to at least: English CD from King's Quest Collection
7851 // Responsible method: sShowLogo::changeState
7852 static const uint16 mothergooseHiresLogoSignature[] = {
7853 0x38, SIG_SELECTOR16(init), // pushi init ($8e)
7854 SIG_MAGICDWORD,
7855 0x76, // push0
7856 0x72, SIG_UINT16(0x0082), // lofsa logo[82]
7857 0x4a, SIG_UINT16(0x0004), // send $4
7858 SIG_END
7859 };
7860
7861 static const uint16 mothergooseHiresLogoPatch[] = {
7862 0x33, 0x08, // jmp [past bad logo init]
7863 PATCH_END
7864 };
7865
7866 // After finishing the rhyme at the fountain, a horse will appear and walk
7867 // across the screen. The priority of the horse is set too high, so it is
7868 // rendered in front of the fountain instead of behind the fountain. This patch
7869 // corrects the priority so the horse draws behind the fountain.
7870 //
7871 // Applies to at least: English CD from King's Quest Collection
7872 // Responsible method: rhymeScript::changeState
7873 static const uint16 mothergooseHiresHorseSignature[] = {
7874 SIG_MAGICDWORD,
7875 0x39, SIG_SELECTOR8(setPri), // pushi setPri ($4a)
7876 0x78, // push1
7877 0x38, SIG_UINT16(0x00b7), // pushi $b7
7878 SIG_END
7879 };
7880
7881 static const uint16 mothergooseHiresHorsePatch[] = {
7882 PATCH_ADDTOOFFSET(+3), // pushi setPri, push1
7883 0x38, PATCH_UINT16(0x59), // pushi $59
7884 PATCH_END
7885 };
7886
7887 // script, description, signature patch
7888 static const SciScriptPatcherEntry mothergooseHiresSignatures[] = {
7889 { true, 0, "disable volume reset on startup (1/2)", 2, sci2VolumeResetSignature, sci2VolumeResetPatch },
7890 { true, 90, "disable volume reset on startup (2/2)", 1, sci2VolumeResetSignature, sci2VolumeResetPatch },
7891 { true, 108, "fix bad logo rendering", 1, mothergooseHiresLogoSignature, mothergooseHiresLogoPatch },
7892 { true, 318, "fix bad horse z-index", 1, mothergooseHiresHorseSignature, mothergooseHiresHorsePatch },
7893 SCI_SIGNATUREENTRY_TERMINATOR
7894 };
7895
7896 #pragma mark -
7897 #pragma mark Phantasmagoria
7898
7899 // Phantasmagoria persists audio volumes in the save games, but ScummVM manages
7900 // game volumes through the launcher, so stop the game from overwriting the
7901 // ScummVM volumes with volumes from save games.
7902 // Applies to at least: English CD
7903 // Fixes bug: #9700
7904 static const uint16 phant1SavedVolumeSignature[] = {
7905 0x7a, // push2
7906 0x39, 0x08, // pushi 8
7907 0x38, SIG_SELECTOR16(readWord), // pushi readWord ($20b)
7908 0x76, // push0
7909 0x72, SIG_UINT16(0x13c), // lofsa $13c (PREF.DAT)
7910 0x4a, SIG_UINT16(0x04), // send 4
7911 SIG_MAGICDWORD,
7912 0xa1, 0xbc, // sag global[$bc]
7913 0x36, // push
7914 0x43, 0x76, SIG_UINT16(0x04), // callk DoAudio[76], 4
7915 0x7a, // push2
7916 0x76, // push0
7917 0x38, SIG_SELECTOR16(readWord), // pushi readWord ($20b)
7918 0x76, // push0
7919 0x72, SIG_UINT16(0x13c), // lofsa $13c (PREF.DAT)
7920 0x4a, SIG_UINT16(0x04), // send 4
7921 0xa1, 0xbb, // sag global[$bb]
7922 0x36, // push
7923 0x43, 0x75, SIG_UINT16(0x04), // callk DoSound[75], 4
7924 SIG_END
7925 };
7926
7927 static const uint16 phant1SavedVolumePatch[] = {
7928 0x32, PATCH_UINT16(0x0024), // jmp [skip the whole sig, to prefFile::close]
7929 PATCH_END
7930 };
7931
7932 // Phantasmagoria performs an incomplete initialisation of a rat view when
7933 // exiting the alcove in the basement any time after chapter 3 when flag 26 is
7934 // not set. This causes the rat view to be rendered with the same origin and
7935 // priority as the background picture for the game plane, triggering last ditch
7936 // sorting of the screen items in the renderer. This happens to work OK most of
7937 // the time in SSCI because the last ditch sort uses memory handle indexes, and
7938 // the index of the rat seems to usually end up below the index for the
7939 // background pic, so the rat's screen item is submitted before the background,
7940 // ensuring that the background palette overrides the rat view's palette. In
7941 // ScummVM, last ditch sorting operates using the creation order of screen
7942 // items, so the rat ends up always sorting above the background, which causes
7943 // the background palette to get replaced by the rat palette, which corrupts the
7944 // background. This patch stops the game script from initialising the bad rat
7945 // view entirely.
7946 // Applies to at least: English CD, French CD
7947 // Fixes bug: #9957
7948 static const uint16 phant1RatSignature[] = {
7949 SIG_MAGICDWORD,
7950 0x78, // push1
7951 0x39, 0x1a, // pushi $1a
7952 0x45, 0x03, SIG_UINT16(0x0002), // callb [export 3 of script 0], 02
7953 0x18, // not
7954 0x31, 0x18, // bnt $18
7955 SIG_END
7956 };
7957
7958 static const uint16 phant1RatPatch[] = {
7959 0x33, 0x20, // jmp [past rat condition + call]
7960 PATCH_END
7961 };
7962
7963 // In Phantasmagoria the cursor's hover state will not trigger on any of the
7964 // buttons in the main menu after returning to the main menu from a game, or
7965 // when choosing "Quit" on the main menu and then cancelling the quit in the
7966 // confirmation dialogue, until another button has been hovered and unhovered
7967 // once.
7968 // This happens because the quit confirmation dialogue creates its own
7969 // event handling loop which prevents the main event loop from handling the
7970 // cursor leaving the button (which would reset global[193] to 0), and the
7971 // dialogue does not reset global[193] itself, so it remains at 2 until a new
7972 // button gets hovered and unhovered.
7973 // There is not enough space in the confirmation dialogue code to add a reset
7974 // of global[193], so we just remove the check entirely, since it is only used
7975 // to avoid resetting the cursor's view on every mouse movement, and this
7976 // button type is only used on the main menu and the in-game control panel.
7977 //
7978 // Applies to at least: English CD
7979 // Fixes bug: #9977
7980 static const uint16 phant1RedQuitCursorSignature[] = {
7981 SIG_MAGICDWORD,
7982 0x89, 0xc1, // lsg global[193]
7983 0x35, 0x02, // ldi 02
7984 SIG_END
7985 };
7986
7987 static const uint16 phant1RedQuitCursorPatch[] = {
7988 0x33, 0x05, // jmp [past global[193] check]
7989 PATCH_END
7990 };
7991
7992 // In chapter 5, the wine casks in room 20200 shimmer to indicate that they have
7993 // a video to play but a script bug usually prevents them from being clicked.
7994 // The code that determines whether to set a hotspot on the "spikot" object
7995 // is incomplete and out of sync with the code that displays the shimmer and
7996 // determines which script to run when the casks are clicked. As a result, the
7997 // shimmering casks can't be clicked in chapter 5 if their contents were tasted
7998 // in an earlier act. In chapter 6 the shimmering casks can be clicked again.
7999 //
8000 // We fix this by rewriting the logic that sets the hotspot to match the rest of
8001 // the code in the room. The casks can now always be clicked when they shimmer.
8002 // To make room we remove a call to approachVerbs: 0 as that has no effect.
8003 //
8004 // Applies to: All versions
8005 // Responsible method: rm20200:init
8006 static const uint16 phant1WineCaskHotspotSignature[] = {
8007 0x89, 0x6a, // lsg 6a
8008 0x35, 0x06, // ldi 06
8009 0x1a, // eq? [ is chapter 6? ]
8010 0x31, 0x09, // bnt 09
8011 SIG_MAGICDWORD,
8012 0x78, // push1
8013 0x38, SIG_UINT16(0x00d8), // pushi 00d8 [ flag 216 ]
8014 0x45, 0x03, SIG_UINT16(0x0002), // callb proc0_3 [ seen barrel video? ]
8015 0x18, // not
8016 0x2f, 0x0f, // bt 0f
8017 0x89, 0x6a, // lsg 6a
8018 0x35, 0x06, // ldi 06
8019 0x1c, // ne? [ is not chapter 6? ]
8020 0x31, 0x21, // bnt 21 [ skip hotspot ]
8021 0x78, // push1
8022 0x39, 0x1d, // pushi 1d [ flag 29 ]
8023 0x45, 0x03, SIG_UINT16(0x0002), // callb proc0_3 [ tasted wine? ]
8024 0x18, // not
8025 0x31, 0x17, // bnt 17 [ skip hotspot ]
8026 0x38, SIG_SELECTOR16(init), // pushi init
8027 0x76, // push0
8028 0x38, SIG_SELECTOR16(approachVerbs), // pushi approachVerbs
8029 0x78, // push1
8030 0x76, // push0
8031 SIG_ADDTOOFFSET(+11),
8032 0x4a, SIG_UINT16(0x0012), // send 12 [ spikot init: approachVerbs: 0 ... ]
8033 SIG_END
8034 };
8035
8036 static const uint16 phant1WineCaskHotspotPatch[] = {
8037 0x78, // push1
8038 0x38, PATCH_UINT16(0x00d8), // pushi 00d8 [ flag 216 ]
8039 0x45, 0x03, PATCH_UINT16(0x0002), // callb proc0_3 [ seen barrel video? ]
8040 0x2f, 0x30, // bt 30 [ skip hotspot ]
8041 0x39, 0x06, // pushi 06
8042 0x81, 0x6a, // lag 6a
8043 0x04, // sub
8044 0x31, 0x17, // bnt 17 [ show hotspot if chapter 6 ]
8045 0x78, // push1
8046 0x1a, // eq?
8047 0x31, 0x0a, // bnt 0a [ skip mirror test if not chapter 5 ]
8048 0x78, // push1
8049 0x38, PATCH_UINT16(0x0123), // pushi 0123 [ flag 291 ]
8050 0x45, 0x03, PATCH_UINT16(0x0002), // callb proc0_3 [ seen mirror video? ]
8051 0x2f, 0x09, // bt 09 [ set hotspot ]
8052 0x78, // push1
8053 0x39, 0x1d, // pushi 1d [ flag 29 ]
8054 0x45, 0x03, PATCH_UINT16(0x0002), // callb proc0_3 [ tasted wine? ]
8055 0x2f, 0x12, // bt 12 [ skip hotspot ]
8056 0x38, PATCH_SELECTOR16(init), // pushi init
8057 0x76, // push0
8058 PATCH_ADDTOOFFSET(+11),
8059 0x4a, PATCH_UINT16(0x000c), // send 0c [ spikot init: ... ]
8060 PATCH_END
8061 };
8062
8063 // The darkroom in the chapter 7 chase, room 45950, has a bug which deletes the
8064 // chase history file and leaves the game in a state that can't be completed.
8065 //
8066 // Every action during the chase is written to chase.dat in order to support the
8067 // review feature which plays back the entire sequence. rm45950:init tests
8068 // several conditions to see how it should initialize the file. If a previous
8069 // chase.dat exists and the current game was initially started in chapter 7
8070 // then an unusual code path is taken which fails to clear flag 134. This flag
8071 // tells the room that the chase is starting. Upon returning for the book with
8072 // this flag incorrectly set, rm45950:init deletes chase.dat and all history is
8073 // lost. Upon playback, items obtained prior to the darkroom will be skipped
8074 // and become unobtainable, such as the glass shard.
8075 //
8076 // We fix this by clearing flag 134 in the unusual code path so that room 45950
8077 // never tries to initialize the chase history upon returning.
8078 //
8079 // Applies to: All versions
8080 // Responsible method: rm45950:init
8081 static const uint16 phant1DeleteChaseFileSignature[] = {
8082 0x32, SIG_UINT16(0x0148), // jmp 0148 [ end of method ]
8083 SIG_ADDTOOFFSET(+0x36),
8084 0x78, // push1
8085 0x38, SIG_MAGICDWORD, // pushi 0086
8086 SIG_UINT16(0x0086),
8087 0x45, 0x02, SIG_UINT16(0x0002), // callb proc0_2 [ clear flag 134 ]
8088 0x32, SIG_UINT16(0x0107), // jmp 0107 [ end of method ]
8089 SIG_END
8090 };
8091
8092 static const uint16 phant1DeleteChaseFilePatch[] = {
8093 0x32, PATCH_UINT16(0x0036), // jmp 0036 [ clear flag 134 ]
8094 PATCH_END
8095 };
8096
8097 // Quitting ScummVM, returning to the launcher, or terminating the game in any
8098 // way outside of the game's menus during the chapter 7 chase leaves the saved
8099 // game in a broken state which will crash ScummVM when loaded. The chase was
8100 // programmed with the expectation that it could never end under unexpected
8101 // circumstances, which is a bold stance for a Sierra game.
8102 //
8103 // Each saved game consists of the file phantsg.#, and once the chase beings, an
8104 // additional chase file that records all actions for playback. This is named
8105 // chase.dat when the chase is running and chasedun.# when it isn't. The file
8106 // is renamed when transitioning to and from the main menu. The existence of
8107 // chasedun.# tells the main menu that it should put the game in the chapter 7
8108 // section. All of this depends on the chase formally ending through scripts
8109 // that can restore the file name. If the game terminates without this cleanup
8110 // then the chase file is never renamed and can't be associated with its slot.
8111 // The save will then be incorrectly demoted to a non-chapter 7 save. Upon
8112 // attempting to load this broken save, a chase script will attempt to play the
8113 // missing chase.dat, seek beyond the beginning, and fail an assertion in a
8114 // stream class. This never picks up the chase.dat from the previous run due to
8115 // a startup script which deletes stray chase.dat files.
8116 //
8117 // We fix this by copying chasedun.# to chase.dat instead of renaming it. This
8118 // leaves the original file intact along with phantsg.# in case the game
8119 // terminates without the scripts getting a chance to cleanup the file system.
8120 // This patch is currently the only known caller of kFileIOCopy.
8121 //
8122 // Applies to: All versions
8123 // Responsible method: Exported procedure #6 in script 45951
8124 static const uint16 phant1CopyChaseFileSignature[] = {
8125 0x39, 0x03, // pushi 03
8126 0x39, 0x0b, // pushi 0b [ kFileIORename ]
8127 0x39, SIG_SELECTOR8(data), // pushi data
8128 0x76, // push0
8129 0x85, 0x00, // lat 00
8130 0x4a, SIG_UINT16(0x0004), // send 04 [ temp0 data? ]
8131 0x36, // push [ "chasedun.#" ]
8132 0x39, SIG_MAGICDWORD, // pushi data
8133 SIG_SELECTOR8(data),
8134 0x76, // push0
8135 0x85, 0x02, // lat 02
8136 0x4a, SIG_UINT16(0x0004), // send 04 [ temp2 data? ]
8137 0x36, // push [ "chase.dat" ]
8138 0x43, 0x5d, SIG_UINT16(0x0006), // callk FileIO 06
8139 SIG_END
8140 };
8141
8142 static const uint16 phant1CopyChaseFilePatch[] = {
8143 PATCH_ADDTOOFFSET(+2),
8144 0x39, 0x0c, // pushi 0c [ kFileIOCopy ]
8145 PATCH_END
8146 };
8147
8148 // During the chase, the west exit in room 46980 has incorrect logic which kills
8149 // the player if they went to the crypt with the crucifix, among other bugs.
8150 //
8151 // Room 46980 takes place in the chapel and connects the secret passages to the
8152 // crypt. The player initially enters from the west and the crypt is to the
8153 // east. The west exit has incorrect logic with several consequences, but the
8154 // harshest is that if the player came from the crypt without beads then Don is
8155 // waiting for them. This is wrong because if the player has the crucifix then
8156 // there are no beads in the game, and also because Don is still in the crypt
8157 // where the player pushed a statue on him in the previous room.
8158 //
8159 // Sierra eventually fixed this by removing the beads from the equation and
8160 // swapping the transposed previous room test, but the fix only appears in the
8161 // Italian version, which was the final CD release. We replace the incorrect
8162 // logic with Sierra's final version.
8163 //
8164 // Applies to: All versions except Italian
8165 // Responsible method: westExit:doVerb
8166 static const uint16 phant1ChapelWestExitSignature[] = {
8167 SIG_MAGICDWORD,
8168 0x38, SIG_SELECTOR16(has), // pushi has
8169 0x78, // push1
8170 0x39, 0x0f, // pushi 0f
8171 0x81, 0x00, // lag 00
8172 0x4a, SIG_UINT16(0x0006), // send 06 [ ego has: 15 ]
8173 0x2f, 0x06, // bt 06
8174 0x89, 0x0c, // lsg 0c
8175 0x34, SIG_UINT16(0xb680), // ldi b680
8176 0x1a, // eq? [ previous room == 46720 ]
8177 SIG_END
8178 };
8179
8180 static const uint16 phant1ChapelWestExitPatch[] = {
8181 0x32, PATCH_UINT16(0x000a), // jmp 000a [ skip inventory check ]
8182 PATCH_ADDTOOFFSET(+15),
8183 0x1c, // ne? [ previous room != 46720 ]
8184 PATCH_END
8185 };
8186
8187 // When reviewing the chapter 7 chase, the game fails to reset the flag that's
8188 // set when Adrian stabs Don and escapes the nursery. This can only happen once
8189 // and so the stale flag causes the review feature to play the wrong animation
8190 // of Don overpowering Adrian instead of Adrian stabbing Don and escaping.
8191 //
8192 // We fix this as Sierra did in the Italian version by clearing flag 18 during
8193 // the chase initialization.
8194 //
8195 // Applies to: All versions except Italian
8196 // Responsible method: sChaseBegin:changeState(0)
8197 static const uint16 phant1ResetStabDonFlagSignature[] = {
8198 SIG_MAGICDWORD,
8199 0x78, // push1
8200 0x38, SIG_UINT16(0x019f), // pushi 019f
8201 SIG_ADDTOOFFSET(+4),
8202 0x7e, SIG_ADDTOOFFSET(+2), // line
8203 0x78, // push1
8204 0x39, 0x7b, // pushi 7b
8205 0x45, 0x02, SIG_UINT16(0x0002), // callb proc0_2 [ clear flag 123 ]
8206 0x7e, SIG_ADDTOOFFSET(+2), // line
8207 0x89, 0x0c, // lsg 0c
8208 0x34, SIG_UINT16(0x0384), // ldi 0384
8209 0x1a, // eq?
8210 0x18, // not
8211 SIG_END
8212 };
8213
8214 static const uint16 phant1ResetStabDonFlagPatch[] = {
8215 PATCH_ADDTOOFFSET(+8),
8216 0x78, // push1
8217 0x39, 0x7b, // pushi 7b
8218 0x45, 0x02, PATCH_UINT16(0x0002), // callb proc0_2 [ clear flag 123 ]
8219 0x78, // push1
8220 0x39, 0x12, // pushi 12
8221 0x45, 0x02, PATCH_UINT16(0x0002), // callb proc0_2 [ clear flag 18 ]
8222 0x89, 0x0c, // lsg 0c
8223 0x34, PATCH_UINT16(0x0384), // ldi 0384
8224 0x1c, // ne?
8225 PATCH_END
8226 };
8227
8228 // The censorship for videos 1920 and 2020 are out of sync and the latter has
8229 // an incorrect coordinate. The frame numbers for the blobs are wrong and so
8230 // they appear during normal frames but not during the gore. These videos play
8231 // in room 40100 when attempting to open or unbar the door. We fix the frame
8232 // numbers to be in sync with the videos and use the correct coordinate from
8233 // video 1920 which has the same censored frames.
8234 //
8235 // Applies to: All versions
8236 // Responsible method: myList:init
8237 static const uint16 phant1Video1920CensorSignature[] = {
8238 0x38, SIG_UINT16(0x00dd), // pushi 221 [ blob 1 start frame ]
8239 SIG_ADDTOOFFSET(+43),
8240 0x39, SIG_MAGICDWORD, 0xff, // pushi -1
8241 0x38, SIG_UINT16(0x00e1), // pushi 225 [ blob 1 end frame ]
8242 SIG_ADDTOOFFSET(+20),
8243 0x38, SIG_UINT16(0x00fb), // pushi 251 [ blob 2 start frame ]
8244 SIG_ADDTOOFFSET(+46),
8245 0x38, SIG_UINT16(0x0117), // pushi 279 [ blob 2 end frame ]
8246 SIG_END
8247 };
8248
8249 static const uint16 phant1Video1920CensorPatch[] = {
8250 0x38, PATCH_UINT16(0x00c6), // pushi 198 [ blob 1 start frame ]
8251 PATCH_ADDTOOFFSET(+45),
8252 0x38, PATCH_UINT16(0x00ca), // pushi 202 [ blob 1 end frame ]
8253 PATCH_ADDTOOFFSET(+20),
8254 0x38, PATCH_UINT16(0x00e4), // pushi 228 [ blob 2 start frame ]
8255 PATCH_ADDTOOFFSET(+46),
8256 0x38, PATCH_UINT16(0x00fe), // pushi 254 [ blob 2 end frame ]
8257 PATCH_END
8258 };
8259
8260 static const uint16 phant1Video2020CensorSignature[] = {
8261 0x38, SIG_UINT16(0x014f), // pushi 335 [ blob 1 start frame ]
8262 SIG_ADDTOOFFSET(+18),
8263 0x38, SIG_UINT16(0x0090), // pushi 144 [ blob 1 left coordinate ]
8264 SIG_ADDTOOFFSET(+23),
8265 0x39, SIG_MAGICDWORD, 0xff, // pushi -1
8266 0x38, SIG_UINT16(0x0153), // pushi 339 [ blob 1 end frame ]
8267 SIG_ADDTOOFFSET(+20),
8268 0x38, SIG_UINT16(0x0160), // pushi 352 [ blob 2 start frame ]
8269 SIG_ADDTOOFFSET(+46),
8270 0x38, SIG_UINT16(0x016a), // pushi 362 [ blob 2 end frame ]
8271 SIG_END
8272 };
8273
8274 static const uint16 phant1Video2020CensorPatch[] = {
8275 0x38, PATCH_UINT16(0x012f), // pushi 303 [ blob 1 start frame ]
8276 PATCH_ADDTOOFFSET(+18),
8277 0x38, PATCH_UINT16(0x0042), // pushi 66 [ blob 1 left coordinate ]
8278 PATCH_ADDTOOFFSET(+25),
8279 0x38, PATCH_UINT16(0x0133), // pushi 307 [ blob 1 end frame ]
8280 PATCH_ADDTOOFFSET(+20),
8281 0x38, PATCH_UINT16(0x014c), // pushi 332 [ blob 2 start frame ]
8282 PATCH_ADDTOOFFSET(+46),
8283 0x38, PATCH_UINT16(0x0168), // pushi 360 [ blob 2 end frame ]
8284 PATCH_END
8285 };
8286
8287 // script, description, signature patch
8288 static const SciScriptPatcherEntry phantasmagoriaSignatures[] = {
8289 { true, 23, "make cursor red after clicking quit", 1, phant1RedQuitCursorSignature, phant1RedQuitCursorPatch },
8290 { true, 26, "fix video 1920 censorship", 1, phant1Video1920CensorSignature, phant1Video1920CensorPatch },
8291 { true, 26, "fix video 2020 censorship", 1, phant1Video2020CensorSignature, phant1Video2020CensorPatch },
8292 { true, 901, "fix invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch },
8293 { true, 1111, "ignore audio settings from save game", 1, phant1SavedVolumeSignature, phant1SavedVolumePatch },
8294 { true, 20200, "fix broken rat init in sEnterFromAlcove", 1, phant1RatSignature, phant1RatPatch },
8295 { true, 20200, "fix chapter 5 wine cask hotspot", 1, phant1WineCaskHotspotSignature, phant1WineCaskHotspotPatch },
8296 { true, 45950, "fix chase file deletion", 1, phant1DeleteChaseFileSignature, phant1DeleteChaseFilePatch },
8297 { true, 45950, "reset stab don flag", 1, phant1ResetStabDonFlagSignature, phant1ResetStabDonFlagPatch },
8298 { true, 45951, "copy chase file instead of rename", 1, phant1CopyChaseFileSignature, phant1CopyChaseFilePatch },
8299 { true, 46980, "fix chapel chase west exit", 1, phant1ChapelWestExitSignature, phant1ChapelWestExitPatch },
8300 { true, 64908, "disable video benchmarking", 1, sci2BenchmarkSignature, sci2BenchmarkPatch },
8301 SCI_SIGNATUREENTRY_TERMINATOR
8302 };
8303
8304 #pragma mark -
8305 #pragma mark Phantasmagoria 2
8306
8307 // The game uses a spin loop when navigating to and from Curtis's computer in
8308 // the office, and when entering passwords, which causes the mouse to appear
8309 // unresponsive. Replace the spin loop with a call to ScummVM kWait.
8310 // Applies to at least: US English
8311 // Responsible method: Script 3000 localproc 2ee4, script 63019 localproc 4f04
8312 static const uint16 phant2WaitParam1Signature[] = {
8313 SIG_MAGICDWORD,
8314 0x35, 0x00, // ldi 0
8315 0xa5, 0x00, // sat temp[0]
8316 0x8d, 0x00, // lst temp[0]
8317 0x87, 0x01, // lap param[1]
8318 SIG_END
8319 };
8320
8321 static const uint16 phant2WaitParam1Patch[] = {
8322 0x78, // push1
8323 0x8f, 0x01, // lsp param[1]
8324 0x43, kScummVMWaitId, PATCH_UINT16(0x02), // callk Wait, 2
8325 0x48, // ret
8326 PATCH_END
8327 };
8328
8329 // The interface bars at the top and bottom of the screen fade in and out when
8330 // hovered over. This fade is performed by a script loop that calls kFrameOut
8331 // directly and uses global[227] as the fade delta for each frame. It normally
8332 // is set to 1, which means that these fades are quite slow. We replace the use
8333 // of global[227] with an immediate value for a reasonable fade speed.
8334 // Applies to at least: US English
8335 static const uint16 phant2SlowIFadeSignature[] = {
8336 0x43, 0x21, SIG_UINT16(0x0000), // callk FrameOut, 0
8337 SIG_MAGICDWORD,
8338 0x67, 0x03, // pTos 03 (scratch)
8339 0x81, 0xe3, // lag global[227]
8340 SIG_END
8341 };
8342
8343 static const uint16 phant2SlowIFadePatch[] = {
8344 PATCH_ADDTOOFFSET(+6), // skip to lag
8345 0x35, 0x05, // ldi 5
8346 PATCH_END
8347 };
8348
8349 // The game uses a spin loop during music transitions which causes the mouse to
8350 // appear unresponsive during scene changes. Replace the spin loop with a call
8351 // to ScummVM kWait.
8352 // Applies to at least: US English
8353 // Responsible method: P2SongPlyr::wait4Fade
8354 static const uint16 phant2Wait4FadeSignature[] = {
8355 SIG_MAGICDWORD,
8356 0x76, // push0
8357 0x43, 0x79, SIG_UINT16(0x0000), // callk GetTime, 0
8358 0xa5, 0x01, // sat temp[1]
8359 0x78, // push1
8360 SIG_END
8361 };
8362
8363 static const uint16 phant2Wait4FadePatch[] = {
8364 0x78, // push1
8365 0x8d, 0x00, // lst temp[0]
8366 0x43, kScummVMWaitId, PATCH_UINT16(0x02), // callk Wait, 2
8367 0x48, // ret
8368 PATCH_END
8369 };
8370
8371 // When reading the VERSION file, Phant2 sends a Str object instead of a
8372 // reference to a string (kernel signature violation), and flips the file handle
8373 // and size arguments, so the version file data never actually makes it into the
8374 // game.
8375 // Applies to at least: Phant2 US English CD
8376 static const uint16 phant2GetVersionSignature[] = {
8377 0x36, // push
8378 0x35, 0xff, // ldi $ff
8379 0x1c, // ne?
8380 0x31, 0x0e, // bnt $e
8381 0x39, 0x04, // pushi 4
8382 0x39, 0x05, // pushi 5
8383 SIG_MAGICDWORD,
8384 0x89, 0x1b, // lsg global[$1b]
8385 0x8d, 0x05, // lst temp[5]
8386 0x39, 0x09, // pushi 9
8387 0x43, 0x5d, SIG_UINT16(0x08), // callk FileIO, 8
8388 0x7a, // push2
8389 0x78, // push1
8390 0x8d, 0x05, // lst temp[5]
8391 0x43, 0x5d, SIG_UINT16(0x04), // callk FileIO, 4
8392 0x35, 0x01, // ldi 1
8393 0xa1, 0xd8, // sag global[$d8]
8394 SIG_END
8395 };
8396
8397 static const uint16 phant2GetVersionPatch[] = {
8398 0x39, 0x04, // pushi 4
8399 0x39, 0x05, // pushi 5
8400 0x81, 0x1b, // lag global[$1b]
8401 0x39, PATCH_SELECTOR8(data), // pushi data
8402 0x76, // push0
8403 0x4a, PATCH_UINT16(0x0004), // send 4
8404 0x36, // push
8405 0x39, 0x09, // pushi 9
8406 0x8d, 0x05, // lst temp[5]
8407 0x43, 0x5d, PATCH_UINT16(0x08), // callk FileIO, 8
8408 0x7a, // push2
8409 0x78, // push1
8410 0x8d, 0x05, // lst temp[5]
8411 0x43, 0x5d, PATCH_UINT16(0x04), // callk FileIO, 4
8412 0x78, // push1
8413 0xa9, 0xd8, // ssg global[$d8]
8414 PATCH_END
8415 };
8416
8417 // The game uses a spin loop when displaying the success animation of the ratboy
8418 // puzzle, which causes the mouse to appear unresponsive. Replace the spin loop
8419 // with a call to ScummVM kWait.
8420 // Applies to at least: US English
8421 static const uint16 phant2RatboySignature[] = {
8422 0x8d, 0x01, // lst temp[1]
8423 0x35, 0x1e, // ldi $1e
8424 0x22, // lt?
8425 SIG_MAGICDWORD,
8426 0x31, 0x17, // bnt $17 [0c3d]
8427 0x76, // push0
8428 0x43, 0x79, SIG_UINT16(0x00), // callk GetTime, 0
8429 SIG_END
8430 };
8431
8432 static const uint16 phant2RatboyPatch[] = {
8433 0x78, // push1
8434 0x35, 0x1e, // ldi $1e
8435 0x36, // push
8436 0x43, kScummVMWaitId, PATCH_UINT16(0x02), // callk Wait, $2
8437 0x33, 0x14, // jmp [to next outer loop]
8438 PATCH_END
8439 };
8440
8441 // Phant2 has separate in-game volume controls for handling movie volume and
8442 // in-game volume (misleadingly labelled "music volume"), but really needs the
8443 // in-game volume to always be significantly lower than the movie volume in
8444 // order for dialogue in movies to be consistently audible, so patch the
8445 // in-game volume slider to limit it to our maximum.
8446 // Applies to at least: US English
8447 // Fixes bug: #10165
8448 static const uint16 phant2AudioVolumeSignature[] = {
8449 SIG_MAGICDWORD,
8450 0x39, 0x7f, // pushi 127 (clientMax value)
8451 0x39, 0x14, // pushi 20 (clientPageSize value)
8452 SIG_ADDTOOFFSET(+10), // skip other init arguments
8453 0x51, 0x5e, // class P2ScrollBar
8454 SIG_ADDTOOFFSET(+3), // skip send
8455 0xa3, 0x06, // sal local[6] (identifies correct slider)
8456 SIG_END
8457 };
8458
8459 static const uint16 phant2AudioVolumePatch[] = {
8460 0x39, kPhant2VolumeMax, // pushi (our custom volume max)
8461 0x39, 0x14 * kPhant2VolumeMax / 127, // pushi (ratio of original value)
8462 PATCH_END
8463 };
8464
8465 // When censorship is disabled the game sticks <PROTECTED> at the end of every
8466 // save game name, and when it is enabled it pads the save game name with a
8467 // bunch of spaces. This is annoying and not helpful, so just disable all of
8468 // this nonsense.
8469 // Applies to at least: US English
8470 // Fixes bug: #10035
8471 static const uint16 phant2SaveNameSignature1[] = {
8472 SIG_MAGICDWORD,
8473 0x57, 0x4b, SIG_UINT16(0x06), // super SREdit, 6
8474 0x63, // pToa (plane)
8475 SIG_END
8476 };
8477
8478 static const uint16 phant2SaveNamePatch1[] = {
8479 PATCH_ADDTOOFFSET(+4), // super SREdit, 6
8480 0x48, // ret
8481 PATCH_END
8482 };
8483
8484 static const uint16 phant2SaveNameSignature2[] = {
8485 SIG_MAGICDWORD,
8486 0xa5, 0x00, // sat temp[0]
8487 0x39, SIG_SELECTOR8(format), // pushi format
8488 SIG_END
8489 };
8490
8491 static const uint16 phant2SaveNamePatch2[] = {
8492 PATCH_ADDTOOFFSET(+2), // sat temp[0]
8493 0x33, 0x68, // jmp [past name mangling]
8494 PATCH_END
8495 };
8496
8497 // Phant2-specific version of sci2NumSavesSignature1/2
8498 // Applies to at least: English CD
8499 static const uint16 phant2NumSavesSignature1[] = {
8500 SIG_MAGICDWORD,
8501 0x8d, 0x01, // lst temp[1]
8502 0x35, 0x14, // ldi 20
8503 0x1a, // eq?
8504 SIG_END
8505 };
8506
8507 static const uint16 phant2NumSavesPatch1[] = {
8508 PATCH_ADDTOOFFSET(+2), // lst temp[1]
8509 0x35, 0x63, // ldi 99
8510 PATCH_END
8511 };
8512
8513 static const uint16 phant2NumSavesSignature2[] = {
8514 SIG_MAGICDWORD,
8515 0x8d, 0x00, // lst temp[0]
8516 0x35, 0x14, // ldi 20
8517 0x22, // lt?
8518 SIG_END
8519 };
8520
8521 static const uint16 phant2NumSavesPatch2[] = {
8522 PATCH_ADDTOOFFSET(+2), // lst temp[0]
8523 0x35, 0x63, // ldi 99
8524 PATCH_END
8525 };
8526
8527 // The game script responsible for handling document scrolling in the computer
8528 // interface uses a spin loop to wait for 10 ticks every time the document
8529 // scrolls. This makes scrolling janky and makes the mouse appear
8530 // non-responsive. Eliminating the delay entirely makes scrolling with the arrow
8531 // buttons a little too quick; a delay of 3 ticks is an OK middle-ground between
8532 // allowing mostly fluid motion with mouse dragging and reasonably paced
8533 // scrolling holding down the arrows. Preferably, ScrollbarArrow::handleEvent or
8534 // ScrollbarArrow::action would only send cues once every N ticks whilst being
8535 // held down, but unfortunately the game was not programmed to do this.
8536 // Applies to at least: US English
8537 static const uint16 phant2SlowScrollSignature[] = {
8538 SIG_MAGICDWORD,
8539 0x35, 0x0a, // ldi 10
8540 0x22, // lt?
8541 0x31, 0x17, // bnt [end of loop]
8542 0x76, // push0
8543 0x43, 0x79, SIG_UINT16(0x0000), // callk GetTime, 0
8544 SIG_END
8545 };
8546
8547 static const uint16 phant2SlowScrollPatch[] = {
8548 0x78, // push1
8549 0x39, 0x03, // pushi 3
8550 0x43, kScummVMWaitId, PATCH_UINT16(0x02), // callk Wait, 2
8551 0x33, 0x13, // jmp [end of loop]
8552 PATCH_END
8553 };
8554
8555 // WynNetDoco::open calls setSize before it calls posn, but the values set by
8556 // posn are used by setSize, so the left/top coordinates of the text and note
8557 // fields is wrong for the first render of a document or email. (Incidentally,
8558 // these fields are the now-seen rect fields, and the game is doing a very bad
8559 // thing by touching these manually and then relying on the values instead of
8560 // asking the kernel.) This is most noticeable during chapters 1 and 3 when the
8561 // computer is displaying scary messages, since every time the scary message is
8562 // rendered the text fields re-render at the top-left corner of the screen.
8563 // Applies to at least: US English
8564 // Fixes bug: #10036
8565 static const uint16 phant2BadPositionSignature[] = {
8566 SIG_MAGICDWORD,
8567 0x39, SIG_SELECTOR8(setSize), // pushi setSize
8568 0x76, // push0
8569 0x39, SIG_SELECTOR8(init), // pushi init
8570 0x78, // pushi 1
8571 0x89, 0x03, // lsg global[3]
8572 0x39, SIG_SELECTOR8(posn), // pushi posn
8573 0x7a, // push2
8574 0x66, SIG_ADDTOOFFSET(+2), // pTos (x position)
8575 0x66, SIG_ADDTOOFFSET(+2), // pTos (y position)
8576 SIG_END
8577 };
8578
8579 static const uint16 phant2BadPositionPatch[] = {
8580 0x39, PATCH_SELECTOR8(posn), // pushi posn
8581 0x7a, // push2
8582 0x66, PATCH_GETORIGINALUINT16(12), // pTos (x position)
8583 0x66, PATCH_GETORIGINALUINT16(15), // pTos (y position)
8584 0x39, PATCH_SELECTOR8(setSize), // pushi setSize
8585 0x76, // push0
8586 0x39, PATCH_SELECTOR8(init), // pushi init
8587 0x78, // pushi 1
8588 0x89, 0x03, // lsg global[3]
8589 PATCH_END
8590 };
8591
8592 // WynDocuStore::refresh resets the cel of the open folder and document icons,
8593 // so they don't end up being rendered as closed folder/document icons, but it
8594 // forgets to actually update the icon's View with the kernel, so they render
8595 // as closed for the first render after a refresh anyway. This is most
8596 // noticeable during chapters 1 and 3 when the computer is displaying scary
8597 // messages, since every time the scary message is rendered the icons re-render
8598 // as closed.
8599 // Applies to at least: US English
8600 static const uint16 phant2BadIconSignature[] = {
8601 SIG_MAGICDWORD,
8602 0x38, SIG_SELECTOR16(setCel), // pushi setCel
8603 0x78, // push1
8604 0x78, // push1
8605 0x38, SIG_SELECTOR16(iconV), // pushi iconV
8606 0x76, // push0
8607 0x62, SIG_ADDTOOFFSET(+2), // pToa curFolder/curDoco
8608 0x4a, SIG_UINT16(0x0004), // send 4
8609 0x4a, SIG_UINT16(0x0006), // send 6
8610 SIG_END
8611 };
8612
8613 static const uint16 phant2BadIconPatch[] = {
8614 PATCH_ADDTOOFFSET(+5), // pushi setCel, push1, push1
8615 0x38, PATCH_SELECTOR16(update), // pushi update
8616 0x76, // push0
8617 0x4a, PATCH_UINT16(0x000a), // send 10
8618 0x33, 0x04, // jmp [past unused bytes]
8619 PATCH_END
8620 };
8621
8622 // The left and right arrows move inventory items a pixel more than each
8623 // inventory item is wide, which causes the inventory to creep to the left by
8624 // one pixel per scrolled item.
8625 // Applies to at least: US English
8626 // Fixes bug: #10037
8627 static const uint16 phant2InvLeftDeltaSignature[] = {
8628 SIG_MAGICDWORD,
8629 SIG_UINT16(0x0042), // delta
8630 SIG_UINT16(0x0019), // moveDelay
8631 SIG_END
8632 };
8633
8634 static const uint16 phant2InvLeftDeltaPatch[] = {
8635 PATCH_UINT16(0x0041), // delta
8636 PATCH_END
8637 };
8638
8639 static const uint16 phant2InvRightDeltaSignature[] = {
8640 SIG_MAGICDWORD,
8641 SIG_UINT16(0xffbe), // delta
8642 SIG_UINT16(0x0019), // moveDelay
8643 SIG_END
8644 };
8645
8646 static const uint16 phant2InvRightDeltaPatch[] = {
8647 PATCH_UINT16(0xffbf), // delta
8648 PATCH_END
8649 };
8650
8651 // The first inventory item is put too far to the right, which causes wide items
8652 // to get cut off on the right side of the inventory.
8653 // Applies to at least: US English
8654 static const uint16 phant2InvOffsetSignature[] = {
8655 SIG_MAGICDWORD,
8656 0x35, 0x26, // ldi 38
8657 0x64, SIG_SELECTOR16(xOff), // aTop xOff
8658 SIG_END
8659 };
8660
8661 static const uint16 phant2InvOffsetPatch[] = {
8662 0x35, 0x1d, // ldi 29
8663 PATCH_END
8664 };
8665
8666 // The text placement of "File" and "Note" content inside DocuStore File
8667 // Retrieval System makes some letters especially "g" overlap the
8668 // corresponding box. Set by 'WynNetDoco::open'.
8669 // We fix this by changing the position of those 2 inside the heap of
8670 // subclass 'WynNetDoco' slightly.
8671 // Applies to at least: English CD, Japanese CD, German CD
8672 // Fixes bug: #10034
8673 static const uint16 phant2DocuStoreFileNotePlacementSignature[] = {
8674 SIG_MAGICDWORD,
8675 SIG_UINT16(0x0046), // nameX
8676 SIG_UINT16(0x000a), // nameY
8677 SIG_ADDTOOFFSET(+10), // skip over nameMsg*
8678 SIG_UINT16(0x0046), // noteX
8679 SIG_UINT16(0x001e), // noteY
8680 SIG_END
8681 };
8682
8683 static const uint16 phant2DocuStoreFileNotePlacementPatch[] = {
8684 PATCH_ADDTOOFFSET(+2),
8685 PATCH_UINT16(0x0006), // new nameY
8686 PATCH_ADDTOOFFSET(+12),
8687 PATCH_UINT16(0x001b), // new noteY
8688 PATCH_END
8689 };
8690
8691 // The text placement of "From" and "Subject" content inside DocuStore.
8692 // We fix this by changing the position inside the heap of subclass
8693 // 'WynNetEmail' slightly.
8694 // For this one, we also fix the horizontal placement.
8695 static const uint16 phant2DocuStoreEmailPlacementSignature[] = {
8696 SIG_MAGICDWORD,
8697 SIG_UINT16(0x0049), // nameX
8698 SIG_UINT16(0x0008), // nameY
8699 SIG_ADDTOOFFSET(+10), // skip over nameMsg*
8700 SIG_UINT16(0x0049), // noteX
8701 SIG_UINT16(0x001c), // noteY
8702 SIG_END
8703 };
8704
8705 static const uint16 phant2DocuStoreEmailPlacementPatch[] = {
8706 PATCH_UINT16(0x0050), // new nameX
8707 PATCH_UINT16(0x0006), // new nameY
8708 PATCH_ADDTOOFFSET(+10),
8709 PATCH_UINT16(0x0050), // new noteX
8710 PATCH_UINT16(0x001b), // new noteY
8711 PATCH_END
8712 };
8713
8714 // script, description, signature patch
8715 static const SciScriptPatcherEntry phantasmagoria2Signatures[] = {
8716 { true, 0, "speed up interface fades", 3, phant2SlowIFadeSignature, phant2SlowIFadePatch },
8717 { true, 0, "fix bad arguments to get game version", 1, phant2GetVersionSignature, phant2GetVersionPatch },
8718 { true, 3000, "replace spin loop in alien password window", 1, phant2WaitParam1Signature, phant2WaitParam1Patch },
8719 { true, 4081, "replace spin loop after ratboy puzzle", 1, phant2RatboySignature, phant2RatboyPatch },
8720 { true, 63001, "fix inventory left scroll delta", 1, phant2InvLeftDeltaSignature, phant2InvLeftDeltaPatch },
8721 { true, 63001, "fix inventory right scroll delta", 1, phant2InvRightDeltaSignature, phant2InvRightDeltaPatch },
8722 { true, 63001, "fix inventory wrong initial offset", 1, phant2InvOffsetSignature, phant2InvOffsetPatch },
8723 { true, 63004, "limit in-game audio volume", 1, phant2AudioVolumeSignature, phant2AudioVolumePatch },
8724 { true, 63016, "replace spin loop during music fades", 1, phant2Wait4FadeSignature, phant2Wait4FadePatch },
8725 { true, 63019, "replace spin loop during computer load", 1, phant2WaitParam1Signature, phant2WaitParam1Patch },
8726 { true, 63019, "replace spin loop during computer scrolling", 1, phant2SlowScrollSignature, phant2SlowScrollPatch },
8727 { true, 63019, "fix bad doc/email name & memo positioning", 2, phant2BadPositionSignature, phant2BadPositionPatch },
8728 { true, 63019, "fix bad folder/doc icon refresh", 2, phant2BadIconSignature, phant2BadIconPatch },
8729 { true, 63019, "fix file and note content placement", 1, phant2DocuStoreFileNotePlacementSignature, phant2DocuStoreFileNotePlacementPatch },
8730 { true, 63019, "fix email content placement", 1, phant2DocuStoreEmailPlacementSignature, phant2DocuStoreEmailPlacementPatch },
8731 { true, 64990, "remove save game name mangling (1/2)", 1, phant2SaveNameSignature1, phant2SaveNamePatch1 },
8732 { true, 64990, "increase number of save games (1/2)", 1, phant2NumSavesSignature1, phant2NumSavesPatch1 },
8733 { true, 64990, "increase number of save games (2/2)", 2, phant2NumSavesSignature2, phant2NumSavesPatch2 },
8734 { true, 64994, "remove save game name mangling (2/2)", 1, phant2SaveNameSignature2, phant2SaveNamePatch2 },
8735 SCI_SIGNATUREENTRY_TERMINATOR
8736 };
8737
8738 #endif
8739
8740 // ===========================================================================
8741 // Police Quest 1 VGA
8742
8743 // When briefing is about to start in room 15, other officers will get into the room too.
8744 // When one of those officers gets into the way of ego, they will tell the player to sit down.
8745 // But control will be disabled right at that point. Ego may then go to his seat by himself,
8746 // or more often than not will just stand there. The player is unable to do anything.
8747 //
8748 // Sergeant Dooley will then enter the room. Tell the player to sit down 3 times and after
8749 // that it's game over.
8750 //
8751 // Because the Sergeant is telling the player to sit down, one has to assume that the player
8752 // is meant to still be in control. Which is why this script patch removes disabling of player control.
8753 //
8754 // The script also tries to make ego walk to the chair, but it fails because it gets stuck with other
8755 // actors. So I guess the safest way is to remove all of that and let the player do it manually.
8756 //
8757 // The responsible method seems to use a few hardcoded texts, which is why I have to assume that it's
8758 // not used anywhere else. I also checked all scripts and couldn't find any other calls to it.
8759 //
8760 // This of course also happens when using the original interpreter.
8761 //
8762 // Scripts work like this: manX::doit (script 134) triggers gab::changeState, which then triggers rm015::notify
8763 //
8764 // Applies to at least: English floppy
8765 // Responsible method: gab::changeState (script 152)
8766 // Fixes bug: #5865
8767 static const uint16 pq1vgaSignatureBriefingGettingStuck[] = {
8768 0x76, // push0
8769 0x45, 0x02, 0x00, // callb [export 2 of script 0], 00 (disable control)
8770 0x38, SIG_ADDTOOFFSET(+2), // pushi notify
8771 0x76, // push0
8772 0x81, 0x02, // lag global[2] (get current room)
8773 0x4a, 0x04, // send 04
8774 SIG_MAGICDWORD,
8775 0x8b, 0x02, // lsl local[2]
8776 0x35, 0x01, // ldi 01
8777 0x02, // add
8778 SIG_END
8779 };
8780
8781 static const uint16 pq1vgaPatchBriefingGettingStuck[] = {
8782 0x33, 0x0a, // jmp [to lsl local[2], skip over export 2 and ::notify]
8783 PATCH_END // rm015::notify would try to make ego walk to the chair
8784 };
8785
8786 // When at the police station, you can put or get your gun from your locker.
8787 // The script, that handles this, is buggy. It disposes the gun as soon as
8788 // you click, but then waits 2 seconds before it also closes the locker.
8789 // Problem is that it's possible to click again, which then results in a
8790 // disposed object getting accessed. This happened to work by pure luck in
8791 // SSCI.
8792 // This patch changes the code, so that the gun is actually given away
8793 // when the 2 seconds have passed and the locker got closed.
8794 // Applies to at least: English floppy
8795 // Responsible method: putGun::changeState (script 341)
8796 // Fixes bug: #5705, #6400
8797 static const uint16 pq1vgaSignaturePutGunInLockerBug[] = {
8798 0x35, 0x00, // ldi 00
8799 0x1a, // eq?
8800 0x31, 0x25, // bnt [next state check]
8801 SIG_ADDTOOFFSET(+22), // [skip 22 bytes]
8802 SIG_MAGICDWORD,
8803 0x38, SIG_SELECTOR16(put), // pushi put
8804 0x78, // push1
8805 0x76, // push0
8806 0x81, 0x00, // lag global[0]
8807 0x4a, 0x06, // send 06 - ego::put(0)
8808 0x35, 0x02, // ldi 02
8809 0x65, 0x1c, // aTop 1c (set timer to 2 seconds)
8810 0x33, 0x0e, // jmp [end of method]
8811 0x3c, // dup (state check)
8812 0x35, 0x01, // ldi 01
8813 0x1a, // eq?
8814 0x31, 0x08, // bnt [end of method]
8815 0x39, SIG_SELECTOR8(dispose), // pushi dispose
8816 0x76, // push0
8817 0x72, SIG_UINT16(0x0088), // lofsa 0088
8818 0x4a, 0x04, // send 04 - locker::dispose
8819 SIG_END
8820 };
8821
8822 static const uint16 pq1vgaPatchPutGunInLockerBug[] = {
8823 PATCH_ADDTOOFFSET(+3),
8824 0x31, 0x1c, // bnt [next state check]
8825 PATCH_ADDTOOFFSET(+22),
8826 0x35, 0x02, // ldi 02
8827 0x65, 0x1c, // aTop 1c (set timer to 2 seconds)
8828 0x33, 0x17, // jmp [end of method]
8829 0x3c, // dup (state check)
8830 0x35, 0x01, // ldi 01
8831 0x1a, // eq?
8832 0x31, 0x11, // bnt [end of method]
8833 0x38, PATCH_SELECTOR16(put), // pushi put
8834 0x78, // push1
8835 0x76, // push0
8836 0x81, 0x00, // lag global[0]
8837 0x4a, 0x06, // send 06 - ego::put(0)
8838 PATCH_END
8839 };
8840
8841 // When restoring a saved game, which was made while driving around,
8842 // the game didn't redraw the map. This also happened in Sierra SCI.
8843 //
8844 // The map is a picture resource and drawn over the main picture.
8845 // This is called an "overlay" in SCI. This wasn't implemented properly.
8846 // We fix it by actually implementing it properly.
8847 //
8848 // Applies to at least: English floppy
8849 // Responsible method: rm500::init, changeOverlay::changeState (script 500)
8850 // Fixes bug: #5016
8851 static const uint16 pq1vgaSignatureMapSaveRestoreBug[] = {
8852 0x39, 0x04, // pushi 04
8853 SIG_ADDTOOFFSET(+2), // skip either lsg global[f9] or pTos register
8854 SIG_MAGICDWORD,
8855 0x38, 0x64, 0x80, // pushi 8064
8856 0x76, // push0
8857 0x89, 0x28, // lsg global[28]
8858 0x43, 0x08, 0x08, // callk DrawPic, 8
8859 SIG_END
8860 };
8861
8862 static const uint16 pq1vgaPatchMapSaveRestoreBug[] = {
8863 0x38, PATCH_SELECTOR16(overlay), // pushi overlay
8864 0x7a, // push2
8865 0x89, 0xf9, // lsg global[f9]
8866 0x39, 0x64, // pushi 64 (no transition)
8867 0x81, 0x02, // lag global[2] (current room object)
8868 0x4a, 0x08, // send 08
8869 0x18, // not (waste byte)
8870 PATCH_END
8871 };
8872
8873 // In the first release of PQ1VGA, looking at objects while sitting in the car
8874 // outside of Carol's breaks the game. The objects set Look as an approachVerb,
8875 // causing ego to float towards them without leaving the car and initializing.
8876 //
8877 // We fix this as Sierra did by removing Look from all approachVerbs in room 30.
8878 //
8879 // Applies to: English Floppy without 30.HEP and 30.SCR
8880 // Responsible methods: door:init, harleys:init, willySign:init, carolSign:init,
8881 // carolWindow:init, weeds:init, alley:init, mat:init
8882 // Fixes bug: #5826
8883 static const uint16 pq1vgaSignatureFloatOutsideCarols1[] = {
8884 0x38, SIG_SELECTOR16(approachVerbs), // pushi approachVerbs
8885 SIG_MAGICDWORD,
8886 0x78, // push1
8887 0x78, // push1
8888 0x54, 0x06, // self 06 [ self approachVerbs: 1 ]
8889 SIG_END
8890 };
8891
8892 static const uint16 pq1vgaPatchFloatOutsideCarols1[] = {
8893 0x32, PATCH_UINT16(0x0004), // jmp 0004 [ don't set approachVerbs ]
8894 PATCH_END
8895 };
8896
8897 static const uint16 pq1vgaSignatureFloatOutsideCarols2[] = {
8898 0x38, SIG_SELECTOR16(approachVerbs), // pushi approachVerbs
8899 SIG_MAGICDWORD,
8900 0x7a, // push2
8901 0x78, // push1
8902 0x39, 0x04, // pushi 04
8903 0x54, 0x08, // self 08 [ self approachVerbs: 1 4 ]
8904 SIG_END
8905 };
8906
8907 static const uint16 pq1vgaPatchFloatOutsideCarols2[] = {
8908 PATCH_ADDTOOFFSET(+3),
8909 0x39, 0x01, // pushi 01
8910 PATCH_ADDTOOFFSET(+2),
8911 0x54, 0x06, // self 06 [ self approachVerbs: 4 ]
8912 PATCH_END
8913 };
8914
8915 // script, description, signature patch
8916 static const SciScriptPatcherEntry pq1vgaSignatures[] = {
8917 { true, 30, "float outside carol's (1/2)", 7, pq1vgaSignatureFloatOutsideCarols1, pq1vgaPatchFloatOutsideCarols1 },
8918 { true, 30, "float outside carol's (2/2)", 1, pq1vgaSignatureFloatOutsideCarols2, pq1vgaPatchFloatOutsideCarols2 },
8919 { true, 152, "getting stuck while briefing is about to start", 1, pq1vgaSignatureBriefingGettingStuck, pq1vgaPatchBriefingGettingStuck },
8920 { true, 341, "put gun in locker bug", 1, pq1vgaSignaturePutGunInLockerBug, pq1vgaPatchPutGunInLockerBug },
8921 { true, 500, "map save/restore bug", 2, pq1vgaSignatureMapSaveRestoreBug, pq1vgaPatchMapSaveRestoreBug },
8922 SCI_SIGNATUREENTRY_TERMINATOR
8923 };
8924
8925 // ===========================================================================
8926 // Police Quest 3
8927
8928 // The player can give the locket to Marie on day 6, which was supposed to grant
8929 // 10 points. Sadly no version did so, so it was not possible to complete the game
8930 // with a perfect score (460 points).
8931
8932 // Those 10 points are mentioned in the official Sierra hint book for day 6,
8933 // which is why we consider this to be accurate.
8934
8935 // This bug occurs of course also, when using the original interpreter.
8936
8937 // We fix this issue by granting those 10 points.
8938
8939 // Applies to at least: English PC floppy, English Amiga, German PC floppy
8940 // Responsible method: giveLocket::changeState(1), script 36
8941 // Fixes bug: #9862
8942 static const uint16 pq3SignatureGiveLocketPoints[] = {
8943 // selectors hardcoded in here, it seems all game versions use the same selector ids
8944 0x39, 0x20, // pushi 20h (state)
8945 0x78, // push1
8946 0x78, // push1
8947 0x39, 0x43, // pushi 43h (at)
8948 0x78, // push1
8949 SIG_MAGICDWORD,
8950 0x39, 0x25, // pushi 25h
8951 0x81, 0x09, // lag global[9]
8952 0x4a, 0x06, // send 06 - Inv::at(25h)
8953 0x4a, 0x06, // send 06 - locket::state(1)
8954 0x38, SIG_UINT16(0x009b), // pushi 009bh (owner)
8955 0x76, // push0
8956 0x39, 0x43, // pushi 43h (at)
8957 0x78, // push1
8958 0x39, 0x25, // pushi 25h
8959 0x81, 0x09, // lag global[9]
8960 0x4a, 0x06, // send 06 - Inv:at(25h)
8961 0x4a, 0x04, // send 04 - locket::owner
8962 SIG_END
8963 };
8964
8965 static const uint16 pq3PatchGiveLocketPoints[] = {
8966 // new code for points, 9 bytes
8967 0x7a, // push2
8968 0x38, PATCH_UINT16(0x00ff), // pushi 0x00ff - using last flag slot, seems to be unused
8969 0x39, 0x0a, // pushi 10d - 10 points
8970 0x45, 0x06, 0x04, // callb [export 6 of script 0], 4
8971 // original code
8972 0x39, 0x20, // pushi 20h (state)
8973 0x78, // push1
8974 0x78, // push1
8975 0x39, 0x43, // pushi 43h (at)
8976 0x78, // push1
8977 0x39, 0x25, // pushi 25h
8978 0x81, 0x09, // lag global[9]
8979 0x4a, 0x06, // send 06 - Inv::at(25h)
8980 0x4a, 0x06, // send 06 - locket::state(1)
8981 // optimized code, saving 9 bytes
8982 0x38, PATCH_UINT16(0x009b), // pushi 009bh (owner)
8983 0x76, // push0
8984 0x4a, 0x04, // send 04 - locket::owner
8985 PATCH_END
8986 };
8987
8988 // The doctor's mouth moves too fast in room 36. doctorMouth:cyleSpeed is set to
8989 // one, the maximum speed, unlike any other inset in the game. Most insets use
8990 // the default speed of six and almost all the rest use an even slower speed.
8991 // We set the doctor's mouth to the default speed.
8992 //
8993 // Applies to: All versions
8994 // Responsible method: insetDoctor:init
8995 // Fixes bug: #10255
8996 static const uint16 pq3SignatureDoctorMouthSpeed[] = {
8997 0x38, SIG_MAGICDWORD, // pushi cyleSpeed
8998 SIG_SELECTOR16(cycleSpeed),
8999 0x78, // push1
9000 0x78, // push1
9001 SIG_ADDTOOFFSET(+13),
9002 0x4a, 0x1c, // send 1c [ doctorMouth ... cycleSpeed: 1 ... ]
9003 SIG_END
9004 };
9005
9006 static const uint16 pq3PatchDoctorMouthSpeed[] = {
9007 0x32, PATCH_UINT16(0x0002), // jmp 0002
9008 PATCH_ADDTOOFFSET(+15),
9009 0x4a, 0x16, // send 16 [ don't set cycleSpeed, use default (6) ]
9010 PATCH_END
9011 };
9012
9013 // The house fire on day six reoccurs if you return to the hospital. Flag 66
9014 // triggers the fire sequence and is always set when leaving the hospital on
9015 // day 6. It's then cleared when arriving at the fire.
9016 //
9017 // We add a test for flag 57, which is set at the fire, so that flag 66 isn't
9018 // set a second time. This is also what Sierra did in later versions.
9019 //
9020 // Applies to: English PC VGA Floppy
9021 // Responsible method: outHospital:changeState(6)
9022 // Fixes bug: #11089
9023 static const uint16 pq3SignatureHouseFireRepeats[] = {
9024 0x30, SIG_UINT16(0x0068), // bnt 0068 [ state 7 ]
9025 SIG_ADDTOOFFSET(+82),
9026 SIG_MAGICDWORD,
9027 0x30, SIG_UINT16(0x0006), // bnt 0006 [ don't set fire-started flag ]
9028 0x78, // push1
9029 0x39, 0x42, // pushi 42 [ flag 66 ]
9030 0x45, 0x09, 0x02, // callb proc0_9 [ set fire-started flag ]
9031 0x38, SIG_SELECTOR16(newRoom), // pushi newRoom
9032 0x78, // push1
9033 0x39, 0x19, // pushi 19
9034 0x81, 0x02, // lag 02
9035 0x4a, 0x06, // send 06 [ rm033 newRoom: 25 ]
9036 0x32, SIG_UINT16(0x004c), // jmp 004c [ end of method ]
9037 0x3c, // dup
9038 0x35, 0x07, // ldi 07
9039 0x1a, // eq?
9040 0x30, SIG_UINT16(0x0007), // bnt 0007 [ state 8 ]
9041 0x35, 0x01, // ldi 01
9042 0x65, 0x10, // aTop cycles
9043 0x32, SIG_UINT16(0x003e), // jmp 003e [ end of method ]
9044 SIG_END
9045 };
9046
9047 static const uint16 pq3PatchHouseFireRepeats[] = {
9048 0x30, PATCH_UINT16(0x006c), // bnt 006c [ state 7 ]
9049 PATCH_ADDTOOFFSET(+82),
9050 0x31, 0x0e, // bnt 0e [ don't set fire-started flag ]
9051 0x78, // push1
9052 0x39, 0x39, // pushi 39 [ flag 57 ]
9053 0x45, 0x0a, 0x02, // callb proc0_10 [ have you been to the fire? ]
9054 0x2f, 0x06, // bt 06 [ don't set fire-started flag ]
9055 0x78, // push1
9056 0x39, 0x42, // pushi 42 [ flag 66 ]
9057 0x45, 0x09, 0x02, // callb proc0_9 [ set fire-started flag ]
9058 0x38, PATCH_SELECTOR16(newRoom), // pushi newRoom
9059 0x78, // push1
9060 0x39, 0x19, // pushi 19
9061 0x81, 0x02, // lag 02
9062 0x4a, 0x06, // send 06 [ rm033 newRoom: 25 ]
9063 0x3c, // dup
9064 0x35, 0x07, // ldi 07
9065 0x1a, // eq?
9066 0x31, 0x04, // bnt 04 [ state 8 ]
9067 0x35, 0x01, // ldi 01
9068 0x65, 0x10, // aTop cycles
9069 PATCH_END
9070 };
9071
9072 // script, description, signature patch
9073 static const SciScriptPatcherEntry pq3Signatures[] = {
9074 { true, 33, "prevent house fire repeating", 1, pq3SignatureHouseFireRepeats, pq3PatchHouseFireRepeats },
9075 { true, 36, "give locket missing points", 1, pq3SignatureGiveLocketPoints, pq3PatchGiveLocketPoints },
9076 { true, 36, "doctor mouth speed", 1, pq3SignatureDoctorMouthSpeed, pq3PatchDoctorMouthSpeed },
9077 SCI_SIGNATUREENTRY_TERMINATOR
9078 };
9079
9080
9081 #ifdef ENABLE_SCI32
9082 #pragma mark -
9083 #pragma mark Police Quest 4
9084
9085 // Add support for simultaneous speech & subtitles to the in-game UI.
9086 // The original game code has code paths for lo-res mode but only creates the
9087 // buttons in hi-res mode, so the lo-res code paths are removed to gain more
9088 // space for the patch.
9089 // Applies to: English CD
9090 // Responsible method: iconText::init, iconText::select
9091 static const uint16 pq4CdSpeechAndSubtitlesSignature[] = {
9092 // iconText::init
9093 0x76, // push0
9094 0x43, 0x22, SIG_UINT16(0x00), // callk IsHiRes
9095 0x18, // not
9096 0x31, 0x05, // bnt [skip next 2 opcodes, when hires]
9097 SIG_MAGICDWORD,
9098 0x34, SIG_UINT16(0x2661), // ldi 9825
9099 0x65, 0x78, // aTop mainView
9100 0x89, 0x5a, // lsg global[$5a]
9101 0x35, 0x01, // ldi 1
9102 0x12, // and
9103 0x30, SIG_UINT16(0x1b), // bnt [when in speech mode]
9104 0x76, // push0
9105 0x43, 0x22, SIG_UINT16(0x00), // callk IsHiRes
9106 SIG_ADDTOOFFSET(+45), // skip over the remaining code
9107 0x38, SIG_SELECTOR16(init), // pushi init ($93)
9108 0x76, // push0
9109 0x59, 0x01, // &rest 01
9110 0x57, 0x8f, SIG_UINT16(0x04), // super GCItem, 4
9111 0x48, // ret
9112
9113 // iconText::select
9114 0x38, SIG_SELECTOR16(select), // pushi select ($1c4)
9115 0x76, // push0
9116 0x59, 0x01, // &rest 01
9117 0x57, 0x8f, SIG_UINT16(0x04), // super GCItem, 4
9118 0x89, 0x5a, // lsg global[$5a]
9119 0x35, 0x02, // ldi 2
9120 0x12, // and
9121 0x30, SIG_UINT16(0x001f), // bnt [to currently-in-text-mode code]
9122 SIG_ADDTOOFFSET(+67), // skip over the rest
9123 0x48, // ret
9124 SIG_END
9125 };
9126
9127 static const uint16 pq4CdSpeechAndSubtitlesPatch[] = {
9128 // iconText::init
9129 0x76, // push0
9130 0x41, 0x02, PATCH_UINT16(0x00), // call [our new subroutine which sets view+loop+cel], 0
9131 0x33, 0x40, // jmp [to original init, super GCItem call]
9132 // new code for setting view+loop+cel
9133 0x34, PATCH_UINT16(0x2aeb), // ldi 10987
9134 0x65, 0x78, // aTop mainView - always set this view, because it's used by 2 states
9135 0x89, 0x5a, // lsg global[$5a]
9136 0x35, 0x03, // ldi 3
9137 0x1a, // eq?
9138 0x31, 0x04, // bnt [skip over follow up code]
9139 // speech+subtitles mode
9140 0x78, // push1
9141 0x69, 0x7a, // sTop mainLoop
9142 0x48, // ret
9143 0x89, 0x5a, // lsg global[$5a]
9144 0x35, 0x01, // ldi 1
9145 0x12, // and
9146 0x31, 0x04, // bnt [skip over follow up code]
9147 // subtitles mode
9148 0x76, // push0
9149 0x69, 0x7a, // sTop mainLoop
9150 0x48, // ret
9151 // speech mode
9152 0x34, PATCH_UINT16(0x2ae6), // ldi 10982
9153 0x65, 0x78, // aTop mainView
9154 0x35, 0x0f, // ldi 15
9155 0x65, 0x7a, // aTop mainLoop
9156 0x48, // ret
9157 PATCH_ADDTOOFFSET(+38), // skip to iconText::select
9158
9159 // iconText::select
9160 PATCH_ADDTOOFFSET(+10), // skip over the super code
9161 0xc1, 0x5a, // +ag global[$5a]
9162 0xa1, 0x5a, // sag global[$5a]
9163 0x36, // push
9164 0x35, 0x04, // ldi 4
9165 0x28, // uge?
9166 0x31, 0x03, // bnt [skip over follow up code]
9167 0x78, // push1
9168 0xa9, 0x5a, // ssg global[$5a]
9169 0x76, // push0
9170 0x41, 0x99, PATCH_UINT16(0x00), // call [our new subroutine which sets view+loop+cel, effectively -103], 0
9171 0x33, 0x2f, // jmp [to end of original select, show call]
9172 PATCH_END
9173 };
9174
9175 // When showing the red shoe to Barbie, after showing the police badge but
9176 // before exhausting the normal dialogue tree, the game plays the expected
9177 // dialogue but fails to award points or set an internal flag indicating this
9178 // interaction has occurred (which is needed to progress in the game).
9179 //
9180 // This is because the game checks global[$9a] (dialogue progress flag) instead
9181 // of local[3] (badge shown flag) when interacting with Barbie. The game uses
9182 // the same `shoeShoe::changeState(0)` method for showing the shoe to the young
9183 // woman at the bar earlier in the game, and checks local[3] then, so just
9184 // check local[3] in both cases to prevent the game from appearing to be in an
9185 // unwinnable state just because the player interacted in the "wrong" order.
9186 //
9187 // Applies to at least: English floppy, German floppy, English CD, German CD
9188 // Fixes bug: #9849
9189 static const uint16 pq4BittyKittyShowBarieRedShoeSignature[] = {
9190 // stripper::noun check is for checking, if police badge was shown
9191 SIG_MAGICDWORD,
9192 0x89, 0x9a, // lsg global[$9a]
9193 0x35, 0x02, // ldi 2
9194 0x1e, // gt?
9195 0x30, SIG_UINT16(0x0028), // bnt [skip 2 points code]
9196 0x39, SIG_SELECTOR8(points), // pushi points ($61)
9197 SIG_END
9198 };
9199
9200 static const uint16 pq4BittyKittyShowBarbieRedShoePatch[] = {
9201 0x83, 0x03, // lal local[3]
9202 0x30, PATCH_UINT16(0x002b), // bnt [skip 2 points code]
9203 0x33, 0x01, // jmp 1 (waste some bytes)
9204 PATCH_END
9205 };
9206
9207 // In PQ4, scripts for the city hall action sequences use `ticks`. These
9208 // continue to count down even during inventory interaction, so if the user is
9209 // unable to find the correct inventory item quickly enough for the sequence,
9210 // the game will immediately end with a "game over" once they close the
9211 // inventory and the main game loop resumes. This can seem like a game bug, so
9212 // we change these sequences to use `seconds`, which only tick down by 1 when
9213 // the game returns to the main loop and the wall time has changed, even if many
9214 // seconds have actually elapsed. However, since `seconds` uses absolute
9215 // hardware clock time with a granularity of 1 second, "one" second can actually
9216 // be less than one second if the timer is set in between hardware clock
9217 // seconds, so the values are increased slightly from their equivalent tick
9218 // values to compensate for this.
9219 //
9220 // TODO: The object structure changed in PQ4CD so ticks moved from 0x20 to 0x22.
9221 // Additional signatures/patches will need to be added for CD version.
9222 //
9223 // Applies to at least: English Floppy, German floppy
9224 // Responsible method: metzAttack::changeState(2) - 120 ticks (player needs to draw gun)
9225 // stickScr::changeState(0) - 180 ticks (player needs to tell enemy to drop gun)
9226 // dropStick::changeState(5) - 120 ticks (player needs to tell enemy to turn around)
9227 // turnMetz::changeState(5) - 600/420 ticks (player needs to cuff Metz)
9228 // all in script 390
9229 static const uint16 pq4FloppyCityHallDrawGunTimerSignature[] = {
9230 SIG_MAGICDWORD,
9231 0x4a, SIG_UINT16(0x08), // send 8
9232 0x32, // jmp [ret]
9233 SIG_ADDTOOFFSET(+8), // skip over some code
9234 0x35, 0x78, // ldi $78 (120)
9235 0x65, 0x20, // aTop ticks
9236 SIG_END
9237 };
9238
9239 static const uint16 pq4FloppyCityHallDrawGunTimerPatch[] = {
9240 PATCH_ADDTOOFFSET(+12), // send 8, jmp, skip over some code
9241 0x35, 0x05, // ldi 5 (120t/2s -> 5s)
9242 0x65, 0x1c, // aTop seconds
9243 PATCH_END
9244 };
9245
9246 static const uint16 pq4FloppyCityHallTellEnemyDropWeaponTimerSignature[] = {
9247 SIG_MAGICDWORD,
9248 0x34, SIG_UINT16(0xb4), // ldi $b4 (180)
9249 0x65, 0x20, // aTop ticks
9250 0x32, SIG_UINT16(0x5e), // jmp [to ret]
9251 SIG_END
9252 };
9253
9254 static const uint16 pq4FloppyCityHallTellEnemyDropWeaponTimerPatch[] = {
9255 0x34, PATCH_UINT16(0x05), // ldi 5 (180t/3s -> 5s)
9256 0x65, 0x1c, // aTop seconds
9257 PATCH_END
9258 };
9259
9260 static const uint16 pq4FloppyCityHallTellEnemyTurnAroundTimerSignature[] = {
9261 SIG_MAGICDWORD,
9262 0x4a, SIG_UINT16(0x04), // send 4
9263 0x35, 0x78, // ldi $78 (120)
9264 0x65, 0x20, // aTop ticks
9265 SIG_END
9266 };
9267
9268 static const uint16 pq4FloppyCityHallTellEnemyTurnAroundTimerPatch[] = {
9269 PATCH_ADDTOOFFSET(+3), // send 4
9270 0x35, 0x03, // ldi 3 (120t/2s -> 3s)
9271 0x65, 0x1c, // aTop seconds
9272 PATCH_END
9273 };
9274
9275 static const uint16 pq4FloppyCityHallCuffEnemyTimerSignature[] = {
9276 SIG_MAGICDWORD,
9277 0x34, SIG_UINT16(0x0258), // ldi $258 (600)
9278 0x65, 0x20, // aTop ticks
9279 SIG_ADDTOOFFSET(+3),
9280 0x34, SIG_UINT16(0x01a4), // ldi $1a4 (420)
9281 0x65, 0x20, // aTop ticks
9282 SIG_END
9283 };
9284
9285 static const uint16 pq4FloppyCityHallCuffEnemyTimerPatch[] = {
9286 0x34, PATCH_UINT16(0x0a), // ldi 10 (600t/10s)
9287 0x65, 0x1c, // aTop seconds
9288 PATCH_ADDTOOFFSET(+3),
9289 0x34, PATCH_UINT16(0x07), // ldi 7 (420t/7s)
9290 0x65, 0x1c, // aTop seconds
9291 PATCH_END
9292 };
9293
9294 // The end game action sequence also uses ticks instead of seconds. See the
9295 // description of city hall action sequence issues for more information.
9296 //
9297 // Applies to at least: English Floppy, German floppy, English CD
9298 // Responsible method: comeInLast::changeState(11)
9299 static const uint16 pq4LastActionHeroTimerSignature[] = {
9300 SIG_MAGICDWORD,
9301 0x34, SIG_UINT16(0x012c), // ldi $12c (300)
9302 0x65, SIG_ADDTOOFFSET(+1), // aTop ticks ($20 for floppy, $22 for CD)
9303 SIG_END
9304 };
9305
9306 static const uint16 pq4LastActionHeroTimerPatch[] = {
9307 0x34, PATCH_UINT16(0x0005), // ldi 5 (300t/5s)
9308 0x65, PATCH_GETORIGINALBYTEADJUST(4, -4), // aTop seconds
9309 PATCH_END
9310 };
9311
9312 // script, description, signature patch
9313 static const SciScriptPatcherEntry pq4Signatures[] = {
9314 { true, 9, "add speech+subtitles to in-game UI", 1, pq4CdSpeechAndSubtitlesSignature, pq4CdSpeechAndSubtitlesPatch },
9315 { true, 315, "fix missing points showing barbie the red shoe", 1, pq4BittyKittyShowBarieRedShoeSignature, pq4BittyKittyShowBarbieRedShoePatch },
9316 { true, 390, "change floppy city hall use gun timer", 1, pq4FloppyCityHallDrawGunTimerSignature, pq4FloppyCityHallDrawGunTimerPatch },
9317 { true, 390, "change floppy city hall say 'drop weapon' timer", 1, pq4FloppyCityHallTellEnemyDropWeaponTimerSignature, pq4FloppyCityHallTellEnemyDropWeaponTimerPatch },
9318 { true, 390, "change floppy city hall say 'turn around' timer", 1, pq4FloppyCityHallTellEnemyTurnAroundTimerSignature, pq4FloppyCityHallTellEnemyTurnAroundTimerPatch },
9319 { true, 390, "change floppy city hall use handcuffs timer", 1, pq4FloppyCityHallCuffEnemyTimerSignature, pq4FloppyCityHallCuffEnemyTimerPatch },
9320 { true, 755, "change last action sequence timer", 1, pq4LastActionHeroTimerSignature, pq4LastActionHeroTimerPatch },
9321 { true, 64908, "disable video benchmarking", 1, sci2BenchmarkSignature, sci2BenchmarkPatch },
9322 { true, 64918, "fix Str::strip in floppy version", 1, sci2BrokenStrStripSignature, sci2BrokenStrStripPatch },
9323 { true, 64990, "increase number of save games (1/2)", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
9324 { true, 64990, "increase number of save games (2/2)", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
9325 { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
9326 SCI_SIGNATUREENTRY_TERMINATOR
9327 };
9328
9329 #pragma mark -
9330 #pragma mark Police Quest: SWAT
9331
9332 // The init code that runs when PQ:SWAT starts up unconditionally resets the
9333 // master sound volume to 127, but the game should always use the volume stored
9334 // in ScummVM.
9335 // Applies to at least: English CD
9336 // Fixes bug: #9700
9337 static const uint16 pqSwatVolumeResetSignature[] = {
9338 SIG_MAGICDWORD,
9339 0x38, SIG_SELECTOR16(masterVolume), // pushi masterVolume
9340 0x78, // push1
9341 0x39, 0x7f, // pushi $7f
9342 0x54, SIG_UINT16(0x0006), // self 6
9343 SIG_END
9344 };
9345
9346 static const uint16 pqSwatVolumeResetPatch[] = {
9347 0x32, PATCH_UINT16(0x0006), // jmp 6 [past volume reset]
9348 PATCH_END
9349 };
9350
9351 // script, description, signature patch
9352 static const SciScriptPatcherEntry pqSwatSignatures[] = {
9353 { true, 0, "disable volume reset on startup (1/2)", 1, pqSwatVolumeResetSignature, pqSwatVolumeResetPatch },
9354 { true, 1, "disable volume reset on startup (2/2)", 1, sci2VolumeResetSignature, sci2VolumeResetPatch },
9355 SCI_SIGNATUREENTRY_TERMINATOR
9356 };
9357
9358 #endif
9359
9360 // ===========================================================================
9361 // At the healer's house there is a bird's nest up on the tree.
9362 // The player can throw rocks at it until it falls to the ground.
9363 // The hero will then grab the item, that is in the nest.
9364 //
9365 // When running is active, the hero will not reach the actual destination
9366 // and because of that, the game will get stuck.
9367 //
9368 // We just change the coordinate of the destination slightly, so that walking,
9369 // sneaking and running work.
9370 //
9371 // This bug was fixed by Sierra at least in the Japanese PC-9801 version.
9372 // Applies to at least: English floppy (1.000, 1.012)
9373 // Responsible method: pickItUp::changeState (script 54)
9374 // Fixes bug: #6407
9375 static const uint16 qfg1egaSignatureThrowRockAtNest[] = {
9376 0x4a, 0x04, // send 04 (nest::x)
9377 0x36, // push
9378 SIG_MAGICDWORD,
9379 0x35, 0x0f, // ldi 0f (15d)
9380 0x02, // add
9381 0x36, // push
9382 SIG_END
9383 };
9384
9385 static const uint16 qfg1egaPatchThrowRockAtNest[] = {
9386 PATCH_ADDTOOFFSET(+3),
9387 0x35, 0x12, // ldi 12 (18d)
9388 PATCH_END
9389 };
9390
9391 // script, description, signature patch
9392 static const SciScriptPatcherEntry qfg1egaSignatures[] = {
9393 { true, 54, "throw rock at nest while running", 1, qfg1egaSignatureThrowRockAtNest, qfg1egaPatchThrowRockAtNest },
9394 SCI_SIGNATUREENTRY_TERMINATOR
9395 };
9396
9397 // ===========================================================================
9398 // script 215 of qfg1vga pointBox::doit actually processes button-presses
9399 // during fighting with monsters. It strangely also calls kGetEvent. Because
9400 // the main User::doit also calls kGetEvent it's pure luck, where the event
9401 // will hit. It's the same issue as in freddy pharkas and if you turn DOSBox
9402 // to max cycles, sometimes clicks also won't get registered. Strangely it's
9403 // not nearly as bad as in our sci, but these differences may be caused by
9404 // timing.
9405 // We just reuse the active event, thus removing the duplicate kGetEvent call.
9406 // Applies to at least: English floppy
9407 // Responsible method: pointBox::doit
9408 // Fixes bug: #5038
9409 static const uint16 qfg1vgaSignatureFightEvents[] = {
9410 0x39, SIG_MAGICDWORD,
9411 SIG_SELECTOR8(new), // pushi new
9412 0x76, // push0
9413 0x51, 0x07, // class Event
9414 0x4a, 0x04, // send 04 - call Event::new
9415 0xa5, 0x00, // sat temp[0]
9416 0x78, // push1
9417 0x76, // push0
9418 0x4a, 0x04, // send 04 - read Event::x
9419 0xa5, 0x03, // sat temp[3]
9420 0x76, // push0 (selector y)
9421 0x76, // push0
9422 0x85, 0x00, // lat temp[0]
9423 0x4a, 0x04, // send 04 - read Event::y
9424 0x36, // push
9425 0x35, 0x0a, // ldi 0a
9426 0x04, // sub (poor mans localization) ;-)
9427 SIG_END
9428 };
9429
9430 static const uint16 qfg1vgaPatchFightEvents[] = {
9431 0x38, PATCH_SELECTOR16(curEvent), // pushi curEvent (15a)
9432 0x76, // push0
9433 0x81, 0x50, // lag global[50]
9434 0x4a, 0x04, // send 04 - read User::curEvent -> needs one byte more than previous code
9435 0xa5, 0x00, // sat temp[0]
9436 0x78, // push1
9437 0x76, // push0
9438 0x4a, 0x04, // send 04 - read Event::x
9439 0xa5, 0x03, // sat temp[3]
9440 0x76, // push0 (selector y)
9441 0x76, // push0
9442 0x85, 0x00, // lat temp[0]
9443 0x4a, 0x04, // send 04 - read Event::y
9444 0x39, 0x00, // pushi 00
9445 0x02, // add (waste 3 bytes) - we don't need localization, User::doit has already done it
9446 PATCH_END
9447 };
9448
9449 // Script 814 of QFG1VGA is responsible for showing dialogs. However, the death
9450 // screen message shown when the hero dies in room 64 (ghost room) is too large
9451 // (254 chars long). Since the window header and main text are both stored in
9452 // temp space, this is an issue, as the scripts read the window header, then the
9453 // window text, which erases the window header text because of its length. To
9454 // fix that, we allocate more temp space and move the pointer used for the
9455 // window header a little bit, wherever it's used in script 814.
9456 // Fixes bug: #6139.
9457
9458 // Patch 1: Increase temp space
9459 static const uint16 qfg1vgaSignatureTempSpace[] = {
9460 SIG_MAGICDWORD,
9461 0x3f, 0xba, // link 0xba
9462 0x87, 0x00, // lap param[0]
9463 SIG_END
9464 };
9465
9466 static const uint16 qfg1vgaPatchTempSpace[] = {
9467 0x3f, 0xca, // link 0xca
9468 PATCH_END
9469 };
9470
9471 // Patch 2: Move the pointer used for the window header a little bit
9472 static const uint16 qfg1vgaSignatureDialogHeader[] = {
9473 SIG_MAGICDWORD,
9474 0x5b, 0x04, 0x80, // lea temp[0x80]
9475 0x36, // push
9476 SIG_END
9477 };
9478
9479 static const uint16 qfg1vgaPatchDialogHeader[] = {
9480 0x5b, 0x04, 0x90, // lea temp[0x90]
9481 PATCH_END
9482 };
9483
9484 // When clicking on the crusher in room 331, Ego approaches him to talk to him,
9485 // an action that is handled by moveToCrusher::changeState in script 331. The
9486 // scripts set Ego to move close to the crusher, but when Ego is sneaking instead
9487 // of walking, the target coordinates specified by script 331 are never reached,
9488 // as Ego is making larger steps, and never reaches the required spot. This is an
9489 // edge case that can occur when Ego is set to sneak. Normally, when clicking on
9490 // the crusher, ego is supposed to move close to position 79, 165. We change it
9491 // to 85, 165, which is not an edge case thus the freeze is avoided.
9492 // Fixes bug: #6180
9493 static const uint16 qfg1vgaSignatureMoveToCrusher[] = {
9494 SIG_MAGICDWORD,
9495 0x51, 0x1f, // class Motion
9496 0x36, // push
9497 0x39, 0x4f, // pushi 4f (79 - x)
9498 0x38, SIG_UINT16(0x00a5), // pushi 00a5 (165 - y)
9499 0x7c, // pushSelf
9500 SIG_END
9501 };
9502
9503 static const uint16 qfg1vgaPatchMoveToCrusher[] = {
9504 PATCH_ADDTOOFFSET(+3),
9505 0x39, 0x55, // pushi 55 (85 - x)
9506 PATCH_END
9507 };
9508
9509 // Clicking "Do" on Crusher in room 331 while standing near the card table locks
9510 // up the game. This is due to a script bug which also occurs in the original.
9511 // This is unrelated to bug #6180 in which clicking "Do" on Crusher while
9512 // sneaking also locks up.
9513 //
9514 // rm331:doit sets ego's script to cardScript when ego enters a rectangle around
9515 // the card table and sets ego's script to none when exiting. This assumes that
9516 // ego can't have a different script set. Clicking "Do" on Crusher sets ego's
9517 // script to moveToCrusher. If moveToCrusher causes ego to enter or exit the
9518 // table's rectangle then the script will be stopped. When ego reaches Crusher
9519 // he will have no script to continue the sequence and the game will be stuck
9520 // in handsOff mode.
9521 //
9522 // We fix this by skipping the card table code in rm331:doit if ego already has
9523 // a script other than cardScript. This prevents the card game from interfering
9524 // with running scripts such as moveToCrusher.
9525 //
9526 // This bug was fixed in the Macintosh version by changing the card game to no
9527 // longer involve setting ego's script and removing the code from rm331:doit.
9528 //
9529 // Applies to: PC Floppy
9530 // Responsible method: rm331:doit
9531 // Fixes bug: #10826
9532 static const uint16 qfg1vgaSignatureCrusherCardGame[] = {
9533 SIG_MAGICDWORD,
9534 0x63, 0x12, // pToa script
9535 0x31, 0x02, // bnt 02
9536 0x33, 0x28, // jmp 28 [ card table location tests ]
9537 0x38, SIG_SELECTOR16(script), // pushi script
9538 0x76, // push0
9539 0x81, 0x00, // lag global[0]
9540 0x4a, 0x04, // send 4 [ ego:script? ]
9541 0x31, 0x04, // bnt 04
9542 0x35, 0x00, // ldi 00 [ does nothing ]
9543 0x33, 0x1a, // jmp 1a [ card table location tests ]
9544 SIG_ADDTOOFFSET(+113),
9545 0x39, SIG_SELECTOR8(doit), // pushi doit [ pc version only ]
9546 SIG_END
9547 };
9548
9549 static const uint16 qfg1vgaPatchCrusherCardGame[] = {
9550 0x38, PATCH_SELECTOR16(script), // pushi script
9551 0x76, // push0
9552 0x81, 0x00, // lag global[0]
9553 0x4a, 0x04, // send 4 [ ego:script? ]
9554 0x31, 0x06, // bnt 06
9555 0x74, PATCH_UINT16(0x0ee4), // lofss cardScript
9556 0x1a, // eq?
9557 0x31, 0x75, // bnt 75 [ skip card table location tests ]
9558 0x63, 0x12, // pToa script
9559 0x2f, 0x1a, // bt 1a [ card table location tests ]
9560 PATCH_END
9561 };
9562
9563 // Same pathfinding bug as above, where Ego is set to move to an impossible
9564 // spot when sneaking. In GuardsTrumpet::changeState, we change the final
9565 // location where Ego is moved from 111, 111 to 116, 116.
9566 // target coordinate is really problematic here.
9567 //
9568 // 114, 114 works when the speed slider is all the way up, but doesn't work
9569 // when the speed slider is not.
9570 //
9571 // It seems that this bug was fixed by Sierra for the Macintosh version.
9572 //
9573 // Applies to at least: English PC floppy
9574 // Responsible method: GuardsTrumpet::changeState(8)
9575 // Fixes bug: #6248
9576 static const uint16 qfg1vgaSignatureMoveToCastleGate[] = {
9577 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo
9578 SIG_MAGICDWORD,
9579 0x36, // push
9580 0x39, 0x6f, // pushi 6f (111d)
9581 0x3c, // dup (111d) - coordinates 111, 111
9582 0x7c, // pushSelf
9583 SIG_END
9584 };
9585
9586 static const uint16 qfg1vgaPatchMoveToCastleGate[] = {
9587 PATCH_ADDTOOFFSET(+3),
9588 0x39, 0x74, // pushi 74 (116d), changes coordinates to 116, 116
9589 PATCH_END
9590 };
9591
9592 // Typo in the original Sierra scripts
9593 // Looking at a cheetaur resulted in a text about a Saurus Rex
9594 // The code treats both monster types the same.
9595 // Applies to at least: English floppy
9596 // Responsible method: smallMonster::doVerb
9597 // Fixes bug: #6249
9598 static const uint16 qfg1vgaSignatureCheetaurDescription[] = {
9599 SIG_MAGICDWORD,
9600 0x34, SIG_UINT16(0x01b8), // ldi 01b8
9601 0x1a, // eq?
9602 0x31, 0x16, // bnt 16
9603 0x38, SIG_SELECTOR16(say), // pushi say (0127h)
9604 0x39, 0x06, // pushi 06
9605 0x39, 0x03, // pushi 03
9606 0x78, // push1
9607 0x39, 0x12, // pushi 12 -> monster type Saurus Rex
9608 SIG_END
9609 };
9610
9611 static const uint16 qfg1vgaPatchCheetaurDescription[] = {
9612 PATCH_ADDTOOFFSET(+14),
9613 0x39, 0x11, // pushi 11 -> monster type cheetaur
9614 PATCH_END
9615 };
9616
9617 // In the "funny" room (Yorick's room) in QfG1 VGA, pulling the chain and
9618 // then pressing the button on the right side of the room results in
9619 // a broken game. This also happens in SSCI.
9620 // Problem is that the Sierra programmers forgot to disable the door that
9621 // gets opened by pulling the chain. So when ego falls down and then
9622 // rolls through the door, one method thinks that the player walks through
9623 // it and acts that way and the other method is still doing the roll animation.
9624 // The timer that closes the door (door11) is local[5]. Setting it to 1 during
9625 // happyFace::changeState(0) stops door11::doit from calling goTo6::init, so
9626 // the whole issue is stopped from happening.
9627 //
9628 // Applies to at least: English floppy
9629 // Responsible method: happyFace::changeState, door11::doit
9630 // Fixes bug: #6181
9631 static const uint16 qfg1vgaSignatureFunnyRoomFix[] = {
9632 0x65, 0x14, // aTop 14 (state)
9633 0x36, // push
9634 0x3c, // dup
9635 0x35, 0x00, // ldi 00
9636 0x1a, // eq?
9637 0x30, SIG_UINT16(0x0025), // bnt 0025 [next state]
9638 SIG_MAGICDWORD,
9639 0x35, 0x01, // ldi 01
9640 0xa3, 0x4e, // sal local[4e]
9641 SIG_END
9642 };
9643
9644 static const uint16 qfg1vgaPatchFunnyRoomFix[] = {
9645 PATCH_ADDTOOFFSET(+3),
9646 0x2e, PATCH_UINT16(0x0029), // bt 0029 [next state] - saves 4 bytes
9647 0x35, 0x01, // ldi 01
9648 0xa3, 0x4e, // sal local[4e]
9649 0xa3, 0x05, // sal local[5] (set to 1)
9650 0xa3, 0x05, // and again to make absolutely sure (actually to waste 2 bytes)
9651 PATCH_END
9652 };
9653
9654 // In Yorick's room, room 96, walking in certain spots in front of the rightmost
9655 // door locks up the game. This also occurs in Sierra's interpreter.
9656 //
9657 // rm96:doit runs the script goTo2 when ego enters a rect in front of the door.
9658 // This rect is low enough that ego can collide with the door's boundary
9659 // obstacle on the right and prevent goTo2 from restoring control to the user.
9660 //
9661 // We fix this by raising the bottom of the door rect. Sierra fixed this bug in
9662 // the Mac version by rewriting the door code, switching to control areas, and
9663 // tweaking the sizes and locations of all the relevant objects.
9664 //
9665 // Applies to: PC Floppy
9666 // Responsible method: rm96:doit
9667 // Fixes bug #6410
9668 static const uint16 qfg1vgaSignatureYorickDoorTwoRect[] = {
9669 SIG_MAGICDWORD,
9670 0x38, SIG_UINT16(0x0135), // pushi 0135 [ x = 309 ]
9671 0x39, 0x64, // pushi 64 [ y = 100 ]
9672 0x38, SIG_UINT16(0x013f), // pushi 013f [ x = 319 ]
9673 0x39, 0x70, // pushi 70 [ y = 112 ]
9674 SIG_END
9675 };
9676
9677 static const uint16 qfg1vgaPatchYorickDoorTwoRect[] = {
9678 PATCH_ADDTOOFFSET(+8),
9679 0x39, 0x6d, // pushi 6d [ y = 109 ]
9680 PATCH_END
9681 };
9682
9683 // The player is able to buy (and also steal) potions in the healer's hut
9684 // Strangely Sierra delays the actual buy/get potion code for 60 ticks
9685 // Why they did that is unknown. The code is triggered anyway only after
9686 // the relevant dialog boxes are closed.
9687 //
9688 // This delay causes problems in case the user quickly enters the inventory.
9689 // That's why we change the amount of ticks to 1, so that the remaining states
9690 // are executed right after the dialog boxes are closed.
9691 //
9692 // Applies to at least: English floppy
9693 // Responsible method: cueItScript::changeState
9694 // Fixes bug: #6706
9695 static const uint16 qfg1vgaSignatureHealerHutNoDelay[] = {
9696 0x65, 0x14, // aTop 14 (state)
9697 0x36, // push
9698 0x3c, // dup
9699 0x35, 0x00, // ldi 00
9700 0x1a, // eq?
9701 0x31, 0x07, // bnt 07 [next state]
9702 SIG_MAGICDWORD,
9703 0x35, 0x3c, // ldi 3c (60 ticks)
9704 0x65, 0x20, // aTop ticks
9705 0x32, // jmp [end of method]
9706 SIG_END
9707 };
9708
9709 static const uint16 qfg1vgaPatchHealerHutNoDelay[] = {
9710 PATCH_ADDTOOFFSET(+9),
9711 0x35, 0x01, // ldi 01 (1 tick only, so that execution will resume as soon as dialog box is closed)
9712 PATCH_END
9713 };
9714
9715 // When following the white stag, you can actually enter the 2nd room from the
9716 // mushroom/fairy location, which results in ego entering from the top. When
9717 // you then throw a dagger at the stag, one animation frame will stay on
9718 // screen, because of a script bug.
9719 //
9720 // Applies to at least: English floppy, Mac floppy
9721 // Responsible method: stagHurt::changeState
9722 // Fixes bug: #6135
9723 static const uint16 qfg1vgaSignatureWhiteStagDagger[] = {
9724 0x87, 0x01, // lap param[1]
9725 0x65, 0x14, // aTop state
9726 0x36, // push
9727 0x3c, // dup
9728 0x35, 0x00, // ldi 0
9729 0x1a, // eq?
9730 0x31, 0x16, // bnt [next parameter check]
9731 0x76, // push0
9732 0x45, 0x02, 0x00, // callb [export 2 of script 0], 0
9733 SIG_MAGICDWORD,
9734 0x38, SIG_SELECTOR16(say), // pushi say (0127h)
9735 0x39, 0x05, // pushi 05
9736 0x39, 0x03, // pushi 03
9737 0x39, 0x51, // pushi 51h
9738 0x76, // push0
9739 0x76, // push0
9740 0x7c, // pushSelf
9741 0x81, 0x5b, // lag global[5Bh] -> qg1Messager
9742 0x4a, 0x0e, // send 0Eh -> qg1Messager::say(3, 51h, 0, 0, stagHurt)
9743 0x33, 0x12, // jmp [end of method]
9744 0x3c, // dup
9745 0x35, 0x01, // ldi 1
9746 0x1a, // eq?
9747 0x31, 0x0c, // bnt [end of method]
9748 0x38, // pushi...
9749 SIG_ADDTOOFFSET(+11),
9750 0x3a, // toss
9751 0x48, // ret
9752 SIG_END
9753 };
9754
9755 static const uint16 qfg1vgaPatchWhiteStagDagger[] = {
9756 PATCH_ADDTOOFFSET(+4),
9757 0x2f, 0x05, // bt [next check] (state != 0)
9758 // state = 0 code
9759 0x35, 0x01, // ldi 1
9760 0x65, 0x1a, // aTop cycles
9761 0x48, // ret
9762 0x36, // push
9763 0x35, 0x01, // ldi 1
9764 0x1a, // eq?
9765 0x31, 0x16, // bnt [state = 2 code]
9766 // state = 1 code
9767 0x76, // push0
9768 0x45, 0x02, 0x00, // callb [export 2 of script 0], 0
9769 0x38, PATCH_SELECTOR16(say), // pushi say (0127h)
9770 0x39, 0x05, // pushi 05
9771 0x39, 0x03, // pushi 03
9772 0x39, 0x51, // pushi 51h
9773 0x76, // push0
9774 0x76, // push0
9775 0x7c, // pushSelf
9776 0x81, 0x5b, // lag global[5Bh] -> qg1Messager
9777 0x4a, 0x0e, // send 0Eh -> qg1Messager::say(3, 51h, 0, 0, stagHurt)
9778 0x48, // ret
9779 // state = 2 code
9780 PATCH_ADDTOOFFSET(+13),
9781 0x48, // ret (remove toss)
9782 PATCH_END
9783 };
9784
9785 // The dagger range has a script bug that can freeze the game or cause Brutus
9786 // to kill hero even after Brutus dies.
9787 //
9788 // When Bruno leaves, a 300 tick countdown starts. If hero kills Brutus or
9789 // leaves room 73 within those 300 ticks, then the game is left in a broken
9790 // state. For the rest of the game, if hero ever returns to the dagger range
9791 // from the east or west during the first half of the day, then the game will
9792 // freeze or Brutus, dead or not, will kill hero.
9793 //
9794 // Special thanks, credits and kudos to sluicebox, who did a ton of research on
9795 // this and even found this game bug originally.
9796 //
9797 // Applies to at least: English floppy, Mac floppy
9798 // Responsible method: brutusWaits::changeState
9799 // Fixes bug: #9558
9800 static const uint16 qfg1vgaSignatureBrutusScriptFreeze[] = {
9801 0x78, // push1
9802 0x38, SIG_UINT16(0x0144), // pushi 144h (324d)
9803 0x45, 0x05, 0x02, // callb [export 5 of script 0], 2
9804 SIG_MAGICDWORD,
9805 0x34, SIG_UINT16(0x012c), // ldi 12Ch (300d)
9806 0x65, 0x20, // aTop ticks
9807 SIG_END
9808 };
9809
9810 static const uint16 qfg1vgaPatchBrutusScriptFreeze[] = {
9811 0x34, PATCH_UINT16(0x0000), // ldi 0 (waste 7 bytes)
9812 0x35, 0x00, // ldi 0
9813 0x35, 0x00, // ldi 0
9814 PATCH_END
9815 };
9816
9817 // Patch the speed test so that it always ends up at the highest level. This
9818 // improves the detail in Yorick's room (96), and slightly changes the timing
9819 // in other rooms. This is compatible with PC and Mac versions which use
9820 // significantly different tests and calculations.
9821 //
9822 // Applies to: PC Floppy, Mac Floppy
9823 // Responsible method: speedTest:changeState(2)
9824 static const uint16 qfg1vgaSignatureSpeedTest[] = {
9825 0x76, // push0
9826 0x43, 0x42, 0x00, // callk GetTime, 0
9827 SIG_MAGICDWORD,
9828 0x36, // push
9829 0x83, 0x01, // lal 01
9830 0x04, // sub
9831 0xa3, 0x00, // sal 00
9832 SIG_END
9833 };
9834
9835 static const uint16 qfg1vgaPatchSpeedTest[] = {
9836 0x35, 0x00, // ldi 00 [ local0 = 0, the best result ]
9837 0x33, 0x04, // jmp 04
9838 PATCH_END
9839 };
9840
9841 // QFG1VGA has a bug where exceeding the weight limit during certain scenes
9842 // breaks the character screen for the rest of the game. Picking mushrooms,
9843 // searching cheetaurs, and fetching the seed are among the vulnerable actions.
9844 //
9845 // When adding inventory, ego:get displays a warning if the new items exceed the
9846 // weight limit. If this happens while qfgMessager is displaying a message
9847 // then both will display at the same time but only one will be disposed. This
9848 // leaves an extra entry in the kernel's window list for the rest of the game.
9849 // kDisplay then sends text to the wrong window, breaking the character screen
9850 // and others, and prevents the player from ever viewing their stats.
9851 //
9852 // We fix this by adding a check to ego:get that skips displaying messages if a
9853 // dialog already exists. This is what Sierra did in the Mac version after
9854 // reverting the scene-specific patches they issued for the PC version.
9855 //
9856 // Applies to: PC Floppy
9857 // Responsible method: ego:get
9858 // Fixes bug: #10942
9859 static const uint16 qfg1vgaSignatureInventoryWeightWarn[] = {
9860 0x8f, 0x00, // lsp 00
9861 0x35, 0x01, // ldi 01
9862 0x1a, // eq?
9863 0x31, 0x04, // bnt 04
9864 0x35, 0x01, // ldi 01
9865 0x33, 0x02, // jmp 02
9866 0x87, 0x02, // lap 02
9867 0xa5, SIG_MAGICDWORD, 0x01, // sat 01
9868 0x38, SIG_UINT16(0x024d), // pushi amount [ hard-coded for PC ]
9869 0x76, // push0
9870 0x85, 0x00, // lat 00
9871 0x4a, 0x04, // send 04 [ temp0 amount? ]
9872 0xa5, 0x02, // sat 02
9873 SIG_ADDTOOFFSET(+0x0092),
9874 0x8d, 0x01, // lst 01
9875 SIG_END
9876 };
9877
9878 static const uint16 qfg1vgaPatchInventoryWeightWarn[] = {
9879 0x87, 0x00, // lap 00
9880 0x78, // push1 [ save 1 byte ]
9881 0x1a, // eq?
9882 0x2f, 0x02, // bt 02 [ save 4 bytes ]
9883 0x87, 0x02, // lap 02
9884 0xa5, 0x01, // sat 01
9885 0x38, PATCH_UINT16(0x024d), // pushi amount [ hard-coded for PC ]
9886 0x76, // push0
9887 0x85, 0x00, // lat 00
9888 0x4a, 0x04, // send 04 [ temp0 amount? ]
9889 0xa5, 0x02, // sat 02
9890 0x81, 0x19, // lag 19 [ dialog ]
9891 0x2e, PATCH_UINT16(0x0092), // bt 0092 [ skip messages if dialog ]
9892 PATCH_END
9893 };
9894
9895 // The baby antwerps in room 78 lockup the game if they get in ego's way when
9896 // exiting south. They also crash the interpreter if they wander too far off
9897 // the screen. These problems also occur in Sierra's interpreter.
9898 //
9899 // The antwerps are controlled by the Wander motion which randomly moves them
9900 // around. There's nothing stopping them from moving past the southern edge of
9901 // the screen and they tend to do so. When ego moves south of 180, sExitAll
9902 // disables control and moves ego off screen, but if an antwerp is in the way
9903 // then ego stops and the user never regains control. Once an antwerp has
9904 // escaped the screen it continues to wander until its position overflows
9905 // several minutes later. This freezes Sierra's interpreter and currently
9906 // causes ours to fail an assertion due to constructing an invalid Rect.
9907 //
9908 // We fix both problems by not allowing antwerps south of 180 so that they
9909 // remain on screen and can't block ego's exit. This is consistent with
9910 // room 85 where they also appear but without a southern exit. The Wander
9911 // motion is only used by antwerps and the sparkles above Yorick in room 96,
9912 // so it can be safely patched to enforce a southern limit. We make room for
9913 // this patch by replacing Wander's calculations with their known results,
9914 // since the default Wander:distance of 30 is always used, and by overwriting
9915 // Wander:onTarget which no script calls and just returns zero.
9916 //
9917 // Applies to: PC Floppy, Mac Floppy
9918 // Responsible method: Wander:setTarget
9919 // Fixes bug: #9564
9920 static const uint16 qfg1vgaSignatureAntwerpWander[] = {
9921 SIG_MAGICDWORD,
9922 0x3f, 0x01, // link 01
9923 0x78, // push1
9924 0x76, // push0
9925 0x63, 0x12, // pToa client
9926 0x4a, 0x04, // send 4
9927 0x36, // push
9928 0x67, 0x30, // pTos distance
9929 0x7a, // push2
9930 0x76, // push0
9931 0x67, 0x30, // pTos distance
9932 0x35, 0x02, // ldi 02
9933 0x06, // mul
9934 0xa5, 0x00, // sat temp[0] [ distance * 2 ]
9935 0x36, // push
9936 0x43, 0x3c, 0x04, // callk Random, 4
9937 0x04, // sub
9938 0x02, // add
9939 0x65, 0x16, // aTop x [ x = client:x + (distance - Random(0, temp[0])) ]
9940 0x76, // push0
9941 0x76, // push0
9942 0x63, 0x12, // pToa client
9943 0x4a, 0x04, // send 4
9944 0x36, // push
9945 0x67, 0x30, // pTos distance
9946 0x7a, // push2
9947 0x76, // push0
9948 0x8d, 0x00, // lst temp[0]
9949 0x43, 0x3c, 0x04, // callk Random, 4
9950 0x04, // sub
9951 0x02, // add
9952 0x65, 0x18, // aTop y [ y = client:y + (distance - Random(0, temp[0])) ]
9953 0x48, // ret
9954 0x35, 0x00, // ldi 00 [ start of Wander:onTarget, returns 0 and isn't called ]
9955 SIG_END
9956 };
9957
9958 static const uint16 qfg1vgaPatchAntwerpWander[] = {
9959 0x78, // push1
9960 0x76, // push0
9961 0x63, 0x12, // pToa client
9962 0x4a, 0x04, // send 4
9963 0x36, // push
9964 0x39, 0x1e, // pushi 1e
9965 0x7a, // push2
9966 0x76, // push0
9967 0x39, 0x3c, // pushi 3c
9968 0x43, 0x3c, 0x04, // callk Random, 4
9969 0x04, // sub
9970 0x02, // add
9971 0x65, 0x16, // aTop x [ x = client:x + (30d - Random(0, 60d)) ]
9972 0x76, // push0
9973 0x76, // push0
9974 0x63, 0x12, // pToa client
9975 0x4a, 0x04, // send 4
9976 0x36, // push
9977 0x39, 0x1e, // pushi 1e
9978 0x7a, // push2
9979 0x76, // push0
9980 0x39, 0x3c, // pushi 3c
9981 0x43, 0x3c, 0x04, // callk Random, 4
9982 0x04, // sub
9983 0x02, // add
9984 0x7a, // push2
9985 0x36, // push
9986 0x38, PATCH_UINT16(0x00b4), // pushi 00b4
9987 0x46, PATCH_UINT16(0x03e7), // calle [export 2 of script 999], 4 [ Min ]
9988 PATCH_UINT16(0x0002), 0x04,
9989 0x65, 0x18, // aTop y [ y = Min(client:y + (30d - Random(0, 60d))), 180d) ]
9990 PATCH_END
9991 };
9992
9993 // QFG1VGA Mac disables all controls when the antwerp falls in room 78, killing
9994 // the player by not allowing them to defend themselves.
9995 //
9996 // The antwerp falls in rooms 78 and 85, and the only way to survive is to hold
9997 // up a weapon. These two antwerp scripts were identical in the PC version and
9998 // enabled all menu icons even though most of them couldn't really be used.
9999 // Sierra attempted to improve this in Mac by only enabling the inventory
10000 // icons but instead disabled everything in room 78 by not calling the enable
10001 // procedure.
10002 //
10003 // We fix this by calling the enable procedure like the script in room 85 does.
10004 //
10005 // Applies to: Mac Floppy
10006 // Responsible method: antwerped:changeState(1)
10007 // Fixes bug: #10856
10008 static const uint16 qfg1vgaSignatureMacAntwerpControls[] = {
10009 0x30, SIG_UINT16(0x0033), // bnt 0033 [ state 1 ]
10010 SIG_ADDTOOFFSET(+48),
10011 SIG_MAGICDWORD,
10012 0x32, SIG_UINT16(0x014e), // jmp 014e [ end of method ]
10013 0x3c, // dup
10014 0x35, 0x01, // ldi 01
10015 0x1a, // eq?
10016 0x30, SIG_UINT16(0x0033), // bnt 0033 [ state 2 ]
10017 0x38, SIG_UINT16(0x00f9), // pushi 00f9 [ canControl, hard coded for Mac ]
10018 SIG_END
10019 };
10020
10021 static const uint16 qfg1vgaPatchMacAntwerpControls[] = {
10022 0x30, PATCH_UINT16(0x0030), // bnt 0030 [ state 1 ]
10023 PATCH_ADDTOOFFSET(+48),
10024 0x3c, // dup
10025 0x35, 0x01, // ldi 01
10026 0x1a, // eq?
10027 0x31, 0x37, // bnt 37 [ state 2 ]
10028 0x76, // push0
10029 0x45, 0x03, 0x00, // callb [export 3 of script 0], 00 [ enable all input ]
10030 PATCH_END
10031 };
10032
10033 // The Mac version's Sierra logo and introduction are often skipped when using a
10034 // mouse. This is a bug in the original that accidentally relies on slower
10035 // machines. In DOS these scenes could be skipped by pressing Enter. Sierra
10036 // updated this to include the mouse, but they did this by accepting any event
10037 // type, including mouse-up. These rooms load in response to mouse-down and if
10038 // they finish loading before the button is released then they are skipped.
10039 //
10040 // We fix this by excluding mouse-up events from these room event handlers.
10041 //
10042 // Applies to: Mac Floppy
10043 // Responsible methods: LogoRoom:handleEvent, intro:handleEvent
10044 // Fixes bug: #10937
10045 static const uint16 qfg1vgaSignatureMacLogoIntroSkip[] = {
10046 0x4a, 0x04, // send 04 [ event type? ]
10047 0x31, SIG_ADDTOOFFSET(+1), // bnt [ skip if event:type == none (0) ]
10048 0x39, SIG_ADDTOOFFSET(+1), // pushi claimed
10049 SIG_MAGICDWORD,
10050 0x78, // push1
10051 0x78, // push1
10052 0x87, 0x01, // lap 01
10053 0x4a, 0x06, // send 06 [ event claimed: 1 ]
10054 SIG_END
10055 };
10056
10057 static const uint16 qfg1vgaPatchMacLogoIntroSkip[] = {
10058 0x39, PATCH_GETORIGINALBYTE(+5), // pushi claimed
10059 0x78, // push1
10060 0x78, // push1
10061 0x4a, 0x0a, // send 0a [ event type? claimed: 1 ]
10062 0x38, PATCH_UINT16(0x00fd), // pushi 00fd
10063 0x12, // and
10064 0x31, PATCH_GETORIGINALBYTEADJUST(+3, -8), // bnt [ skip if event:type == none (0) or mouse-up (2)]
10065 PATCH_END
10066 };
10067
10068 // The Thieves' Guild cashier in room 332 stops responding to verbs when he
10069 // reappears at his window. This is due to heGoes:changeState(1) disposing and
10070 // deleting borisThief once he's out of sight, indirectly deleting his actions
10071 // object borisTeller which handles verbs. borisTeller is only initialized in
10072 // rm332:init and this leaves the player unable to purchase or fence items.
10073 //
10074 // We fix this by toggling borisThief's visibility with the hide and show
10075 // methods instead of disposing and re-initializing.
10076 //
10077 // Applies to: PC Floppy, Mac Floppy
10078 // Responsible methods: heComes:changeState, heGoes:changeState
10079 // Fixes bug #10939
10080 static const uint16 qfg1vgaSignatureThievesGuildCashier[] = {
10081 0x30, SIG_UINT16(0x0024), // bnt 0024 [ state 1 ]
10082 SIG_ADDTOOFFSET(+31),
10083 SIG_MAGICDWORD,
10084 0x4a, 0x20, // send 20 [ borisThief ... init: ... ]
10085 0x32, SIG_UINT16(0x002b), // jmp 002b [ end of method ]
10086 0x3c, // dup
10087 0x35, 0x01, // ldi 01
10088 0x1a, // eq?
10089 0x30, SIG_UINT16(0x0019), // bnt 0019 [ state 2 ]
10090 SIG_ADDTOOFFSET(+82),
10091 0x39, SIG_SELECTOR8(dispose), // pushi dispose
10092 0x76, // push0
10093 0x39, SIG_SELECTOR8(delete), // pushi delete
10094 SIG_ADDTOOFFSET(+4),
10095 0x4a, 0x08, // send 08 [ borisThief dispose: delete: ]
10096 SIG_END
10097 };
10098
10099 static const uint16 qfg1vgaPatchThievesGuildCashier[] = {
10100 0x30, PATCH_UINT16(0x0025), // bnt 0025 [ state 1 ]
10101 PATCH_ADDTOOFFSET(+31),
10102 0x38, PATCH_SELECTOR16(show), // pushi show
10103 0x76, // push0
10104 0x4a, 0x24, // send 24 [ borisThief ... init: ... show: ]
10105 0x3c, // dup
10106 0x35, 0x01, // ldi 01
10107 0x1a, // eq?
10108 0x31, 0x19, // bnt 19 [ state 2 ]
10109 PATCH_ADDTOOFFSET(+82),
10110 0x39, PATCH_SELECTOR8(hide), // pushi hide
10111 0x32, PATCH_UINT16(0x0000), // jmp 0000
10112 PATCH_ADDTOOFFSET(+4),
10113 0x4a, 0x04, // send 04 [ borisThief hide: ]
10114 PATCH_END
10115 };
10116
10117 // When entering the great hall (room 141), the Mac version stores ego's speed
10118 // in a temp variable in egoEnters:changeState(0) and expects that value to be
10119 // there in state 6 when restoring ego's speed. We patch the script to use its
10120 // register instead so that it works and doesn't do an uninitialized read.
10121 //
10122 // Applies to: Mac Floppy
10123 // Responsible method: egoEnters:changeState
10124 // Fixes bug: #10945
10125 static const uint16 qfg1vgaSignatureMacEnterGreatHall[] = {
10126 SIG_MAGICDWORD,
10127 0x4a, 0x04, // send 04 [ ego cycleSpeed? ]
10128 0xa5, 0x00, // sat 00
10129 SIG_ADDTOOFFSET(+140),
10130 0x8d, 0x00, // lst 00
10131 SIG_END
10132 };
10133
10134 static const uint16 qfg1vgaPatchMacEnterGreatHall[] = {
10135 PATCH_ADDTOOFFSET(+2),
10136 0x65, 0x24, // aTop register
10137 PATCH_ADDTOOFFSET(+140),
10138 0x67, 0x24, // pTos register
10139 PATCH_END
10140 };
10141
10142 // When fighting the giant in room 58, the Mac version stores the weapon in a
10143 // temp variable in giantFights:changeState(2) and expects that value to be
10144 // there in later states to determine whether or not to run sword animation.
10145 // We patch the script to use the local variable that holds the weapon instead
10146 // so that this works and doesn't do an uninitialized read.
10147 //
10148 // Applies to: Mac Floppy
10149 // Responsible method: giantFights:changeState
10150 // Fixes bug: #10948
10151 static const uint16 qfg1vgaSignatureMacGiantFight[] = {
10152 SIG_MAGICDWORD,
10153 0x8d, 0x00, // lst 00 [ temp0 set to 0 in state 2 if local5 == 1 ]
10154 0x35, 0x00, // ldi 00
10155 0x1a, // eq?
10156 SIG_END
10157 };
10158
10159 static const uint16 qfg1vgaPatchMacGiantFight[] = {
10160 0x8b, 0x05, // lsl 05
10161 0x35, 0x01, // ldi 01
10162 PATCH_END
10163 };
10164
10165 // Dag-Nab-It, the dagger game in room 340, mistakenly leaves inventory enabled.
10166 // Using a throwable item, such as a dagger, locks up the game.
10167 //
10168 // We fix this by disabling inventory controls on this screen. Sierra attempted
10169 // to do this in the Mac version but introduced a new bug which we also fix.
10170 //
10171 // Applies to: PC Floppy
10172 // Responsible method: dagnabitScript:changeState
10173 // Fixes bug: #10958
10174 static const uint16 qfg1vgaSignatureDagnabitInventory[] = {
10175 0x38, SIG_SELECTOR16(disable), // pushi disable
10176 0x39, 0x03, // pushi 03
10177 0x78, // push1
10178 0x39, SIG_MAGICDWORD, 0x05, // pushi 05
10179 0x39, 0x09, // pushi 09
10180 0x81, 0x45, // lag 45
10181 0x4a, 0x0a, // send 0a [ mainIconBar disable: 1 5 9 ]
10182 0x35, 0x01, // ldi 01
10183 0xa3, 0x13, // sal 13
10184 0x32, SIG_UINT16(0x0278), // jmp 0278 [ end of method ]
10185 SIG_ADDTOOFFSET(+625),
10186 0x38, SIG_SELECTOR16(changeState), // pushi changeState
10187 0x78, // push1
10188 0x78, // push1
10189 SIG_END
10190 };
10191
10192 static const uint16 qfg1vgaPatchDagnabitInventory[] = {
10193 PATCH_ADDTOOFFSET(+3),
10194 0x39, 0x05, // pushi 05
10195 PATCH_ADDTOOFFSET(+5),
10196 0x39, 0x07, // pushi 07 [ "use" inventory icon ]
10197 0x39, 0x08, // pushi 08 [ inventory ]
10198 0x81, 0x45, // lag 45
10199 0x4a, 0x0e, // send 0e [ mainIconBar disable: 1 5 9 7 8 ]
10200 0x78, // push1
10201 0xab, 0x13, // ssl 13
10202 PATCH_ADDTOOFFSET(+629),
10203 0x76, // push0 [ state 0 re-disables inventory ]
10204 PATCH_END
10205 };
10206
10207 // The Mac version of Dag-Nab-It, the dagger game in room 340, introduced a bug
10208 // that sends a message to a non-object when clicking during the start.
10209 //
10210 // The PC version left inventory enabled and Sierra fixed this in Mac. Sierra
10211 // also attempted to clear the inventory cursor, but this was done in a way
10212 // that leaves the icon bar in an illegal state. mainIconBar:curInvIcon is
10213 // set to zero but mainIconBar:curIcon remains set to the "use" icon item.
10214 // Clicking anywhere during the initial two seconds causes IconBar:handleEvent
10215 // to query curInvIcon:message but since curInvIcon is zero this is an error.
10216 //
10217 // We fix this with a deceptively simple patch that prevents the "use" icon from
10218 // ending up as mainIconBar:curIcon. rm340:init runs a complex sequence of icon
10219 // disabling and enabling. Patching a redundant mainIconBar:disable to include
10220 // "use" prevents the subsequent call to handsOff from cycling through enabled
10221 // icons and landing on "use" as the new curIcon, preventing the illegal state.
10222 //
10223 // Applies to: Mac Floppy
10224 // Responsible method: rm340:init
10225 // Fixes bug: #10958
10226 static const uint16 qfg1vgaSignatureMacDagnabitIconBar[] = {
10227 0x38, SIG_SELECTOR16(disable), // pushi disable
10228 0x39, 0x03, // pushi 03
10229 0x78, // push1
10230 SIG_MAGICDWORD,
10231 0x39, 0x05, // pushi 05
10232 0x39, 0x08, // pushi 08
10233 0x81, 0x45, // lag 45
10234 0x4a, 0x0a, // send 0a [ mainIconBar disable: 1 5 8 ]
10235 SIG_END
10236 };
10237
10238 static const uint16 qfg1vgaPatchMacDagnabitIconBar[] = {
10239 PATCH_ADDTOOFFSET(+6),
10240 0x39, 0x07, // pushi 07 [ "use" inventory icon ]
10241 PATCH_END
10242 };
10243
10244 // Drinking water in room 87 after talking to the healer about falling water
10245 // shows two messages at once. The second message overwrites the first before
10246 // it is shown. We add a state for the second message as in the Mac version.
10247 //
10248 // Applies to: PC Floppy
10249 // Responsible method: drinkWater:changeState
10250 // Fixes bug: #11086
10251 static const uint16 qfg1vgaSignatureDrinkWaterMessage[] = {
10252 0x31, SIG_MAGICDWORD, 0x29, // bnt 29 [ state 3 handler ]
10253 0x38, SIG_SELECTOR16(say), // pushi say
10254 0x39, 0x05, // pushi 05
10255 0x39, 0x07, // pushi 07
10256 0x76, // push0
10257 0x76, // push0
10258 0x78, // push1
10259 0x7c, // pushSelf
10260 0x81, 0x5b, // lag 5b
10261 0x4a, 0x0e, // send 0e [ qg1Messager say: 7 0 0 1 self ]
10262 0x78, // push1
10263 0x38, SIG_UINT16(0x00c9), // pushi 00c9 [ flag 201 ]
10264 0x45, 0x07, 0x02, // callb proc0_7 [ talked to healer about water? ]
10265 0x31, 0x42, // bnt 42 [ skip second message ]
10266 SIG_ADDTOOFFSET(+9),
10267 0x7a, // push2 [ message seq: 2 ]
10268 SIG_ADDTOOFFSET(+8),
10269 0x35, 0x03, // ldi 03 [ state 3 ]
10270 SIG_ADDTOOFFSET(+18),
10271 0x35, 0x04, // ldi 04 [ state 4 ]
10272 SIG_END
10273 };
10274
10275 static const uint16 qfg1vgaPatchDrinkWaterMessage[] = {
10276 0x2f, 0x14, // bt 14 [ show first message in state 2 ]
10277 0x3c, // dup
10278 0x35, 0x03, // ldi 03 [ state 3 ]
10279 0x1a, // eq?
10280 0x31, 0x23, // bnt 23 [ state 4 handler ]
10281 0x78, // push1
10282 0x38, PATCH_UINT16(0x00c9), // pushi 00c9 [ flag 201 ]
10283 0x45, 0x07, 0x02, // callb proc0_7 [ talked to healer about water? ]
10284 0x2f, 0x05, // bt 05 [ show second message in state 3 ]
10285 0x78, // push1
10286 0x69, 0x1a, // sTop cycles [ cycles = 1 ]
10287 0x3a, // toss
10288 0x48, // ret
10289 0x3c, // dup
10290 0x35, 0x01, // ldi 01
10291 0x04, // sub
10292 PATCH_ADDTOOFFSET(+9),
10293 0x36, // push [ message seq: state - 1 ]
10294 PATCH_ADDTOOFFSET(+8),
10295 0x35, 0x04, // ldi 04 [ state 4 ]
10296 PATCH_ADDTOOFFSET(+18),
10297 0x35, 0x05, // ldi 05 [ state 5 ]
10298 PATCH_END
10299 };
10300
10301 // script, description, signature patch
10302 static const SciScriptPatcherEntry qfg1vgaSignatures[] = {
10303 { true, 0, "inventory weight warning", 1, qfg1vgaSignatureInventoryWeightWarn, qfg1vgaPatchInventoryWeightWarn },
10304 { true, 41, "moving to castle gate", 1, qfg1vgaSignatureMoveToCastleGate, qfg1vgaPatchMoveToCastleGate },
10305 { true, 55, "healer's hut, no delay for buy/steal", 1, qfg1vgaSignatureHealerHutNoDelay, qfg1vgaPatchHealerHutNoDelay },
10306 { true, 58, "mac: giant fight", 6, qfg1vgaSignatureMacGiantFight, qfg1vgaPatchMacGiantFight },
10307 { true, 73, "brutus script freeze glitch", 1, qfg1vgaSignatureBrutusScriptFreeze, qfg1vgaPatchBrutusScriptFreeze },
10308 { true, 77, "white stag dagger throw animation glitch", 1, qfg1vgaSignatureWhiteStagDagger, qfg1vgaPatchWhiteStagDagger },
10309 { true, 78, "mac: enable antwerp controls", 1, qfg1vgaSignatureMacAntwerpControls, qfg1vgaPatchMacAntwerpControls },
10310 { true, 87, "drink water message", 1, qfg1vgaSignatureDrinkWaterMessage, qfg1vgaPatchDrinkWaterMessage },
10311 { true, 96, "funny room script bug fixed", 1, qfg1vgaSignatureFunnyRoomFix, qfg1vgaPatchFunnyRoomFix },
10312 { true, 96, "yorick door #2 lockup fixed", 1, qfg1vgaSignatureYorickDoorTwoRect, qfg1vgaPatchYorickDoorTwoRect },
10313 { true, 141, "mac: enter great hall", 1, qfg1vgaSignatureMacEnterGreatHall, qfg1vgaPatchMacEnterGreatHall },
10314 { true, 200, "mac: intro mouse-up fix", 1, qfg1vgaSignatureMacLogoIntroSkip, qfg1vgaPatchMacLogoIntroSkip },
10315 { true, 210, "cheetaur description fixed", 1, qfg1vgaSignatureCheetaurDescription, qfg1vgaPatchCheetaurDescription },
10316 { true, 215, "fight event issue", 1, qfg1vgaSignatureFightEvents, qfg1vgaPatchFightEvents },
10317 { true, 216, "weapon master event issue", 1, qfg1vgaSignatureFightEvents, qfg1vgaPatchFightEvents },
10318 { true, 299, "speedtest", 1, qfg1vgaSignatureSpeedTest, qfg1vgaPatchSpeedTest },
10319 { true, 331, "moving to crusher", 1, qfg1vgaSignatureMoveToCrusher, qfg1vgaPatchMoveToCrusher },
10320 { true, 331, "moving to crusher from card game", 1, qfg1vgaSignatureCrusherCardGame, qfg1vgaPatchCrusherCardGame },
10321 { true, 332, "thieves' guild cashier fix", 1, qfg1vgaSignatureThievesGuildCashier, qfg1vgaPatchThievesGuildCashier },
10322 { true, 340, "dagnabit inventory fix", 1, qfg1vgaSignatureDagnabitInventory, qfg1vgaPatchDagnabitInventory },
10323 { true, 340, "mac: dagnabit icon bar fix", 1, qfg1vgaSignatureMacDagnabitIconBar, qfg1vgaPatchMacDagnabitIconBar },
10324 { true, 603, "mac: logo mouse-up fix", 1, qfg1vgaSignatureMacLogoIntroSkip, qfg1vgaPatchMacLogoIntroSkip },
10325 { true, 814, "window text temp space", 1, qfg1vgaSignatureTempSpace, qfg1vgaPatchTempSpace },
10326 { true, 814, "dialog header offset", 3, qfg1vgaSignatureDialogHeader, qfg1vgaPatchDialogHeader },
10327 { true, 970, "antwerps wandering off-screen", 1, qfg1vgaSignatureAntwerpWander, qfg1vgaPatchAntwerpWander },
10328 SCI_SIGNATUREENTRY_TERMINATOR
10329 };
10330
10331 // ===========================================================================
10332
10333 // This is a very complicated bug.
10334 // When the player encounters an enemy in the desert while riding a saurus and
10335 // later tries to get back on it by entering "ride", the game will not give
10336 // control back to the player.
10337 //
10338 // This is caused by script mountSaurus getting triggered twice. Once by
10339 // entering the command "ride" and then a second time by a proximity check.
10340 //
10341 // Both are calling mountSaurus::init() in script 20. This one disables
10342 // controls. Then mountSaurus::changeState() from script 660 is triggered.
10343 // Finally, mountSaurus::changeState(5) calls mountSaurus::dispose(), also in
10344 // script 20, which re-enables controls.
10345 //
10346 // A fix is difficult to implement. The code in script 20 is generic and used
10347 // by multiple objects
10348 //
10349 // An early attempt changed the responsible vars (global[102], global[161])
10350 // during mountSaurus::changeState(5). This worked for controls, but
10351 // mountSaurus::init changes a few selectors of ego as well, which won't get
10352 // restored in that situation, which then messes up room changes and other
10353 // things.
10354 //
10355 // Instead we change sheepScript::changeState(2) in script 665.
10356 //
10357 // Note: This could cause issues in case there is a cutscene, where ego is
10358 // supposed to get onto the saurus using sheepScript.
10359 //
10360 // Applies to at least: English PC Floppy, English Amiga Floppy
10361 // Responsible method: mountSaurus::changeState(), mountSaurus::init(), mountSaurus::dispose()
10362 // Fixes bug: #5156
10363 static const uint16 qfg2SignatureSaurusFreeze[] = {
10364 0x3c, // dup
10365 0x35, 0x02, // ldi 2
10366 SIG_MAGICDWORD,
10367 0x1a, // eq?
10368 0x30, SIG_UINT16(0x0043), // bnt [ret]
10369 0x76, // push0
10370 SIG_ADDTOOFFSET(+61), // skip to dispose code
10371 0x39, SIG_SELECTOR8(dispose), // pushi dispose
10372 0x76, // push0
10373 0x54, 0x04, // self 04
10374 SIG_END
10375 };
10376
10377 static const uint16 qfg2PatchSaurusFreeze[] = {
10378 0x81, 0x66, // lag global[66h]
10379 0x2e, PATCH_UINT16(0x0040), // bt [to dispose code]
10380 0x35, 0x00, // ldi 0 (waste 2 bytes)
10381 PATCH_END
10382 };
10383
10384 // The Jackalmen combat code has at least one serious issue.
10385 //
10386 // Jackalmen may attack in groups. This is handled by 2 globals.
10387 // global[136h]: amount of Jackalmen still alive.
10388 // global[137h]: amount of Jackalmen killed so far during combat.
10389 //
10390 // After combat has ended, global[137h] is subtracted from global[136h]. BUT
10391 // when the player manages to hit the last enemy AFTER defeating it during its
10392 // death animation (yes, that is possible - don't ask), the code is called a
10393 // second time. Subtracting global[137h] twice, which will make global[136h]
10394 // negative and will then create an inconsistent state. Some variables will
10395 // show that there is still an enemy, while others don't. The game will crash
10396 // when leaving the room. The original interpreter would show the infamous
10397 // "Oops, you did something we weren't expecting..."
10398 //
10399 // TODO: Check, if patch works for 1.000. That version surely has the same bug.
10400 // Applies to at least: English Floppy (1.102+1.105)
10401 // Responsible method: jackalMan::die (script 695)
10402 // Fixes bug: #10218
10403 static const uint16 qfg2SignatureOopsJackalMen[] = {
10404 SIG_MAGICDWORD,
10405 0x8b, 0x00, // lsl local[0]
10406 0x35, 0x00, // ldi 0
10407 0x22, // lt?
10408 0x30, SIG_UINT16(0x000b), // bnt [Jackalman death animation code]
10409 0x38, SIG_ADDTOOFFSET(+2), // pushi (die)
10410 0x76, // push0
10411 0x57, 0x66, 0x04, // super Monster, 4
10412 0x48, // ret
10413 0x32, SIG_UINT16(0x001a), // jmp (seems to be a compiler bug)
10414 // Jackalman death animation code
10415 0x83, 0x00, // lal local[0]
10416 0x18, // not
10417 0x30, SIG_UINT16(0x0003), // bnt [make next enemy walk in]
10418 SIG_END
10419 };
10420
10421 static const uint16 qfg2PatchOopsJackalMen[] = {
10422 0x80, PATCH_UINT16(0x0136), // lag global[136h]
10423 0x31, 0x0e, // bnt [skip everything] - requires 5 extra bytes
10424 0x8b, 0x00, // lsl local[0]
10425 0x35, 0x00, // ldi 0
10426 0x22, // lt?
10427 0x31, 0x08, // bnt [Jackalman death animation code] (save 1 byte)
10428 0x38, PATCH_GETORIGINALUINT16(+9), // pushi (die)
10429 0x76, // push0
10430 0x57, 0x66, 0x04, // super Monster, 4
10431 0x48, // ret
10432 // Jackalman death animation code
10433 0x83, 0x00, // lal local[0]
10434 0x18, // not
10435 0x31, 0x03, // bnt [make next enemy walk in] (save 1 byte)
10436 PATCH_END
10437 };
10438
10439 // Script 944 in QFG2 contains the FileSelector system class, used in the
10440 // character import screen. This gets incorrectly called constantly, whenever
10441 // the user clicks on a button in order to refresh the file list. This was
10442 // probably done because it would be easier to refresh the list whenever the
10443 // user inserted a new floppy disk, or changed directory. The problem is that
10444 // the script has a bug, and it invalidates the text of the entries in the
10445 // list. This has a high probability of breaking, as the user could change the
10446 // list very quickly, or the garbage collector could kick in and remove the
10447 // deleted entries. We don't allow the user to change the directory, thus the
10448 // contents of the file list are constant, so we can avoid the constant file
10449 // and text entry refreshes whenever a button is pressed, and prevent possible
10450 // crashes because of these constant quick object reallocations.
10451 // Fixes bug: #5096
10452 static const uint16 qfg2SignatureImportDialog[] = {
10453 0x63, SIG_MAGICDWORD, 0x20, // pToa text
10454 0x30, SIG_UINT16(0x000b), // bnt [next state]
10455 0x7a, // push2
10456 0x39, 0x03, // pushi 03
10457 0x36, // push
10458 0x43, 0x72, 0x04, // callk Memory, 4
10459 0x35, 0x00, // ldi 00
10460 0x65, 0x20, // aTop text
10461 SIG_END
10462 };
10463
10464 static const uint16 qfg2PatchImportDialog[] = {
10465 PATCH_ADDTOOFFSET(+5),
10466 0x48, // ret
10467 PATCH_END
10468 };
10469
10470 // Quest For Glory 2 character import doesn't properly set the character type
10471 // in versions 1.102 and below, which makes all imported characters a fighter.
10472 //
10473 // Sierra released an official patch. However the fix is really easy to
10474 // implement on our side, so we also patch the flaw in here in case we find it.
10475 //
10476 // The version released on GOG is 1.102 without this patch applied, so us
10477 // patching it is quite useful.
10478 //
10479 // Applies to at least: English Floppy
10480 // Responsible method: importHero::changeState
10481 // Fixes bug: inside versions 1.102 and below
10482 static const uint16 qfg2SignatureImportCharType[] = {
10483 0x35, 0x04, // ldi 04
10484 0x90, SIG_UINT16(0x023b), // lagi global[23Bh]
10485 0x02, // add
10486 0x36, // push
10487 0x35, 0x04, // ldi 04
10488 0x08, // div
10489 0x36, // push
10490 0x35, 0x0d, // ldi 0D
10491 0xb0, SIG_UINT16(0x023b), // sagi global[023Bh]
10492 0x8b, 0x1f, // lsl local[1Fh]
10493 0x35, 0x05, // ldi 05
10494 SIG_MAGICDWORD,
10495 0xb0, SIG_UINT16(0x0150), // sagi global[0150h]
10496 0x8b, 0x02, // lsl local[02h]
10497 SIG_END
10498 };
10499
10500 static const uint16 qfg2PatchImportCharType[] = {
10501 0x80, PATCH_UINT16(0x023f), // lag global[23Fh] <-- patched to save 2 bytes
10502 0x02, // add
10503 0x36, // push
10504 0x35, 0x04, // ldi 04
10505 0x08, // div
10506 0x36, // push
10507 0xa8, PATCH_UINT16(0x0248), // ssg global[0248h] <-- patched to save 2 bytes
10508 0x8b, 0x1f, // lsl local[1Fh]
10509 0xa8, PATCH_UINT16(0x0155), // ssg global[0155h] <-- patched to save 2 bytes
10510 // new code, directly from the official sierra patch file
10511 0x83, 0x01, // lal local[01h]
10512 0xa1, 0xbb, // sag global[BBh]
10513 0xa1, 0x73, // sag global[73h]
10514 PATCH_END
10515 };
10516
10517 // script, description, signature patch
10518 static const SciScriptPatcherEntry qfg2Signatures[] = {
10519 { true, 665, "getting back on saurus freeze fix", 1, qfg2SignatureSaurusFreeze, qfg2PatchSaurusFreeze },
10520 { true, 695, "Oops Jackalmen fix", 1, qfg2SignatureOopsJackalMen, qfg2PatchOopsJackalMen },
10521 { true, 805, "import character type fix", 1, qfg2SignatureImportCharType, qfg2PatchImportCharType },
10522 { true, 944, "import dialog continuous calls", 1, qfg2SignatureImportDialog, qfg2PatchImportDialog },
10523 SCI_SIGNATUREENTRY_TERMINATOR
10524 };
10525
10526 // ===========================================================================
10527 // Patch for the import screen in QFG3, same as the one for QFG2 above
10528 static const uint16 qfg3SignatureImportDialog[] = {
10529 0x63, SIG_MAGICDWORD, 0x2a, // pToa text
10530 0x31, 0x0b, // bnt [next state]
10531 0x7a, // push2
10532 0x39, 0x03, // pushi 03
10533 0x36, // push
10534 0x43, 0x72, 0x04, // callk Memory, 4
10535 0x35, 0x00, // ldi 00
10536 0x65, 0x2a, // aTop text
10537 SIG_END
10538 };
10539
10540 static const uint16 qfg3PatchImportDialog[] = {
10541 PATCH_ADDTOOFFSET(+4),
10542 0x48, // ret
10543 PATCH_END
10544 };
10545
10546 // Patch for the Woo dialog option in Uhura's conversation.
10547 //
10548 // Problem: The Woo dialog option (0xffb5) is negative, and therefore
10549 // treated as an option opening a submenu. This leads to uhuraTell::doChild
10550 // being called, which calls hero::solvePuzzle and then proceeds with
10551 // Teller::doChild to open the submenu. However, there is no actual submenu
10552 // defined for option -75 since -75 does not show up in uhuraTell::keys.
10553 // This will cause Teller::doChild to run out of bounds while scanning through
10554 // uhuraTell::keys.
10555 //
10556 // Strategy: there is another conversation option in uhuraTell::doChild calling
10557 // hero::solvePuzzle (0xfffc) which does a ret afterwards without going to
10558 // Teller::doChild. We jump to this call of hero::solvePuzzle to get that same
10559 // behaviour.
10560 //
10561 // Applies to at least: English, German, Italian, French, Spanish Floppy
10562 // Responsible method: uhuraTell::doChild
10563 // Fixes bug: #5172
10564 static const uint16 qfg3SignatureWooDialog[] = {
10565 SIG_MAGICDWORD,
10566 0x67, 0x12, // pTos 12 (query)
10567 0x35, 0xb6, // ldi b6
10568 0x1a, // eq?
10569 0x2f, 0x05, // bt 05
10570 0x67, 0x12, // pTos 12 (query)
10571 0x35, 0x9b, // ldi 9b
10572 0x1a, // eq?
10573 0x31, 0x0c, // bnt 0c
10574 0x38, SIG_SELECTOR16(solvePuzzle), // pushi solvePuzzle (0297)
10575 0x7a, // push2
10576 0x38, SIG_UINT16(0x010c), // pushi 010c
10577 0x7a, // push2
10578 0x81, 0x00, // lag global[0]
10579 0x4a, 0x08, // send 08
10580 0x67, 0x12, // pTos 12 (query)
10581 0x35, 0xb5, // ldi b5
10582 SIG_END
10583 };
10584
10585 static const uint16 qfg3PatchWooDialog[] = {
10586 PATCH_ADDTOOFFSET(+0x29),
10587 0x33, 0x11, // jmp [to 0x6a2, the call to hero::solvePuzzle for 0xFFFC]
10588 PATCH_END
10589 };
10590
10591 // Alternative version, with uint16 offsets, for GOG release of QfG3.
10592 static const uint16 qfg3SignatureWooDialogAlt[] = {
10593 SIG_MAGICDWORD,
10594 0x67, 0x12, // pTos 12 (query)
10595 0x35, 0xb6, // ldi b6
10596 0x1a, // eq?
10597 0x2e, SIG_UINT16(0x0005), // bt 05
10598 0x67, 0x12, // pTos 12 (query)
10599 0x35, 0x9b, // ldi 9b
10600 0x1a, // eq?
10601 0x30, SIG_UINT16(0x000c), // bnt 0c
10602 0x38, SIG_SELECTOR16(solvePuzzle), // pushi solvePuzzle (0297)
10603 0x7a, // push2
10604 0x38, SIG_UINT16(0x010c), // pushi 010c
10605 0x7a, // push2
10606 0x81, 0x00, // lag global[0]
10607 0x4a, 0x08, // send 08
10608 0x67, 0x12, // pTos 12 (query)
10609 0x35, 0xb5, // ldi b5
10610 SIG_END
10611 };
10612
10613 static const uint16 qfg3PatchWooDialogAlt[] = {
10614 PATCH_ADDTOOFFSET(+44),
10615 0x33, 0x12, // jmp [to 0x708, the call to hero::solvePuzzle for 0xFFFC]
10616 PATCH_END
10617 };
10618
10619 // When exporting characters at the end of Quest for Glory 3, the underlying
10620 // code has issues with values above 9999.
10621 // For further study: https://github.com/Blazingstix/QFGImporter/blob/master/QFGImporter/QFGImporter/QFG3.txt
10622 //
10623 // If a value is above 9999, parts or even the whole character file will get
10624 // corrupted. We calculate the checksum and add extra code to lower such
10625 // values to 9999.
10626 //
10627 // Applies to at least: English, French, German, Italian, Spanish floppy
10628 // Responsible method: saveHero::changeState
10629 // Fixes bug: #6807
10630 static const uint16 qfg3SignatureExportChar[] = {
10631 0x35, SIG_ADDTOOFFSET(+1), // ldi 00 / ldi 01 (2 loops, we patch both)
10632 0xa5, 0x00, // sat temp[0] [contains index to data]
10633 0x8d, 0x00, // lst temp[0]
10634 SIG_MAGICDWORD,
10635 0x35, 0x2c, // ldi 2c
10636 0x22, // lt? (index above or equal to 2Ch (44d)?)
10637 0x31, 0x23, // bnt [exit loop]
10638 // from this point it's actually useless code, maybe a sci compiler bug
10639 0x8d, 0x00, // lst temp[0]
10640 0x35, 0x01, // ldi 01
10641 0x02, // add
10642 0x9b, 0x00, // lsli local[0] ---------- load local[0 + ACC] onto stack
10643 0x8d, 0x00, // lst temp[0]
10644 0x35, 0x01, // ldi 01
10645 0x02, // add
10646 0xb3, 0x00, // sali local[0] ---------- save stack to local[0 + ACC]
10647 // end of useless code
10648 0x8b, SIG_ADDTOOFFSET(+1), // lsl local[36h/37h] ----- load local[36h/37h] onto stack
10649 0x8d, 0x00, // lst temp[0]
10650 0x35, 0x01, // ldi 01
10651 0x02, // add
10652 0x93, 0x00, // lali local[0] ---------- load local[0 + ACC] into ACC
10653 0x02, // add -------------------- add ACC + stack and put into ACC
10654 0xa3, SIG_ADDTOOFFSET(+1), // sal local[36h/37h] ----- save ACC to local[36h/37h]
10655 0x8d, 0x00, // lst temp[0] ------------ temp[0] to stack
10656 0x35, 0x02, // ldi 02
10657 0x02, // add -------------------- add 2 to stack
10658 0xa5, 0x00, // sat temp[0] ------------ save ACC to temp[0]
10659 0x33, 0xd6, // jmp [loop]
10660 SIG_END
10661 };
10662
10663 static const uint16 qfg3PatchExportChar[] = {
10664 PATCH_ADDTOOFFSET(+11),
10665 0x85, 0x00, // lat temp[0]
10666 0x9b, 0x01, // lsli local[0] + 1 ------ load local[ ACC + 1] onto stack
10667 0x3c, // dup
10668 0x34, PATCH_UINT16(0x2710), // ldi 2710h (10000d)
10669 0x2c, // ult? ------------------- is value smaller than 10000?
10670 0x2f, 0x0a, // bt [jump over]
10671 0x3a, // toss
10672 0x38, PATCH_UINT16(0x270f), // pushi 270fh (9999d)
10673 0x3c, // dup
10674 0x85, 0x00, // lat temp[0]
10675 0xba, PATCH_UINT16(0x0001), // ssli local[0] + 1 ------ save stack to local[ACC + 1] (UINT16 to waste 1 byte)
10676 // jump offset
10677 0x83, PATCH_GETORIGINALBYTE(+26), // lal local[37h/36h] ----- load local[37h/36h] into ACC
10678 0x02, // add -------------------- add local[37h/36h] + data value
10679 PATCH_END
10680 };
10681
10682 // Quest for Glory 3 doesn't properly import the character type of QFG1
10683 // character files. This issue was never addressed. It's caused by Sierra
10684 // reading data directly from the local area, which is only set by QFG2
10685 // import data, instead of reading the properly set global variable.
10686 //
10687 // We fix it, by also directly setting the local variable.
10688 //
10689 // Applies to at least: English, French, German, Italian, Spanish floppy
10690 // Responsible method: importHero::changeState(4)
10691 static const uint16 qfg3SignatureImportQfG1Char[] = {
10692 SIG_MAGICDWORD,
10693 0x82, SIG_UINT16(0x0238), // lal local[0x0238]
10694 0xa0, SIG_UINT16(0x016a), // sag global[0x016a]
10695 0xa1, 0x7d, // sag global[0x7d]
10696 0x35, 0x01, // ldi 01
10697 0x99, 0xfb, // lsgi global[0xfb]
10698 SIG_END
10699 };
10700
10701 static const uint16 qfg3PatchImportQfG1Char[] = {
10702 PATCH_ADDTOOFFSET(+8),
10703 0xa3, 0x01, // sal local[1]
10704 0x89, 0xfc, // lsg global[0xfc] (save 2 bytes vs global[0xfb + 1])
10705 PATCH_END
10706 };
10707
10708 // The chief in his hut (room 640) is not drawn using the correct priority,
10709 // which results in a graphical glitch. This is a game bug and also happens
10710 // in Sierra's SCI. We adjust priority accordingly to fix it.
10711 //
10712 // Applies to at least: English, French, German, Italian, Spanish floppy
10713 // Responsible method: heap in script 640
10714 // Fixes bug: #5173
10715 static const uint16 qfg3SignatureChiefPriority[] = {
10716 SIG_MAGICDWORD,
10717 SIG_UINT16(0x0002), // yStep 0x0002
10718 SIG_UINT16(0x0281), // view 0x0281
10719 SIG_UINT16(0x0000), // loop 0x0000
10720 SIG_UINT16(0x0000), // cel 0x0000
10721 SIG_UINT16(0x0000), // priority 0x0000
10722 SIG_UINT16(0x0000), // underbits 0x0000
10723 SIG_UINT16(0x1000), // signal 0x1000
10724 SIG_END
10725 };
10726
10727 static const uint16 qfg3PatchChiefPriority[] = {
10728 PATCH_ADDTOOFFSET(+8),
10729 PATCH_UINT16(0x000a), // priority 0x000A (10d)
10730 PATCH_ADDTOOFFSET(+2),
10731 PATCH_UINT16(0x1010), // signal 0x1010 (set fixed priority flag)
10732 PATCH_END
10733 };
10734
10735 // There are 3 points that can't be achieved in the game. They should've been
10736 // awarded for telling Rakeesh and Kreesha (room 285) about the Simabni
10737 // initiation.
10738 // However the array of posibble messages the hero can tell in that room
10739 // (local[156]) is missing the "Tell about Initiation" message (#31) which
10740 // awards these points.
10741 // This patch adds the message to that array, thus allowing the hero to tell
10742 // that message (after completing the initiation) and gain the 3 points.
10743 // A side effect of increasing the local[156] array is that the next local
10744 // array is shifted and shrinks in size from 4 words to 3. The patch changes
10745 // the 2 locations in the script that reference that array, to point to the new
10746 // location (local[$aa] --> local[$ab]). It is safe to shrink the 2nd array to
10747 // 3 words because only the first element in it is ever used.
10748 //
10749 // Note: You have to re-enter the room in case a saved game was loaded from a
10750 // previous version of ScummVM and that saved game was made inside that room.
10751 //
10752 // Applies to: English, French, German, Italian, Spanish and the GOG release.
10753 // Responsible method: heap in script 285
10754 // Fixes bug: #7086
10755 static const uint16 qfg3SignatureMissingPoints1[] = {
10756 // local[$9c] = [0 -41 -76 1 -30 -77 -33 -34 -35 -36 -37 -42 -80 999]
10757 // local[$aa] = [0 0 0 0]
10758 SIG_UINT16(0x0000), // 0 START MARKER
10759 SIG_MAGICDWORD,
10760 SIG_UINT16(0xffd7), // -41 "Greet"
10761 SIG_UINT16(0xffb4), // -76 "Say Good-bye"
10762 SIG_UINT16(0x0001), // 1 "Tell about Tarna"
10763 SIG_UINT16(0xffe2), // -30 "Tell about Simani"
10764 SIG_UINT16(0xffb3), // -77 "Tell about Prisoner"
10765 SIG_UINT16(0xffdf), // -33 "Dispelled Leopard Lady"
10766 SIG_UINT16(0xffde), // -34 "Tell about Leopard Lady"
10767 SIG_UINT16(0xffdd), // -35 "Tell about Leopard Lady"
10768 SIG_UINT16(0xffdc), // -36 "Tell about Leopard Lady"
10769 SIG_UINT16(0xffdb), // -37 "Tell about Village"
10770 SIG_UINT16(0xffd6), // -42 "Greet"
10771 SIG_UINT16(0xffb0), // -80 "Say Good-bye"
10772 SIG_UINT16(0x03e7), // 999 END MARKER
10773 SIG_ADDTOOFFSET(+2), // local[$aa][0]
10774 SIG_END
10775 };
10776
10777 static const uint16 qfg3PatchMissingPoints1[] = {
10778 PATCH_ADDTOOFFSET(+14),
10779 PATCH_UINT16(0xffe1), // -31 "Tell about Initiation"
10780 PATCH_UINT16(0xffde), // -34 "Tell about Leopard Lady"
10781 PATCH_UINT16(0xffdd), // -35 "Tell about Leopard Lady"
10782 PATCH_UINT16(0xffdc), // -36 "Tell about Leopard Lady"
10783 PATCH_UINT16(0xffdb), // -37 "Tell about Village"
10784 PATCH_UINT16(0xffd6), // -42 "Greet"
10785 PATCH_UINT16(0xffb0), // -80 "Say Good-bye"
10786 PATCH_UINT16(0x03e7), // 999 END MARKER
10787 PATCH_GETORIGINALUINT16(+28), // local[$aa][0]
10788 PATCH_END
10789 };
10790
10791 static const uint16 qfg3SignatureMissingPoints2a[] = {
10792 SIG_MAGICDWORD,
10793 0x35, 0x00, // ldi 0
10794 0xb3, 0xaa, // sali local[$aa]
10795 SIG_END
10796 };
10797
10798 static const uint16 qfg3SignatureMissingPoints2b[] = {
10799 SIG_MAGICDWORD,
10800 0x36, // push
10801 0x5b, 0x02, 0xaa, // lea local[$aa]
10802 SIG_END
10803 };
10804
10805 static const uint16 qfg3PatchMissingPoints2[] = {
10806 PATCH_ADDTOOFFSET(+3),
10807 0xab, // local[$ab] (replace local[$aa])
10808 PATCH_END
10809 };
10810
10811 // Partly WORKAROUND:
10812 // During combat, the game is not properly throttled. That's because the game uses
10813 // an inner loop for combat and does not iterate through the main loop.
10814 // It also doesn't call kGameIsRestarting. This may get fixed properly at some point
10815 // by rewriting the speed throttler.
10816 //
10817 // Additionally Sierra set the cycle speed of the hero to 0. Which explains
10818 // why the actions of the hero are so incredibly fast. This issue also happened
10819 // in the original interpreter, when the computer was too powerful.
10820 //
10821 // Applies to at least: English, French, German, Italian, Spanish PC floppy
10822 // Responsible method: combatControls::dispatchEvent (script 550) + WarriorObj in heap
10823 // Fixes bug: #6247
10824 static const uint16 qfg3SignatureCombatSpeedThrottling1[] = {
10825 0x3f, 0x03, // link 3d (these temp vars are never used)
10826 0x76, // push0
10827 0x43, 0x42, 0x00, // callk GetTime, 0d
10828 0xa1, 0x58, // sag global[88]
10829 0x36, // push
10830 0x83, 0x01, // lal local[1]
10831 SIG_ADDTOOFFSET(+3), // ...
10832 SIG_MAGICDWORD,
10833 0x89, 0xd2, // lsg global[210]
10834 0x35, 0x00, // ldi 0
10835 0x1e, // gt?
10836 SIG_ADDTOOFFSET(+6), // ...
10837 0xa3, 0x01, // sal local[1]
10838 SIG_END
10839 };
10840
10841 static const uint16 qfg3PatchCombatSpeedThrottling1[] = {
10842 0x76, // push0 (no link, freed +2 bytes)
10843 0x43, 0x42, 0x00, // callk GetTime, 0d
10844 0xa1, 0x58, // sag global[88] (no push, leave time in acc)
10845 0x8b, 0x01, // lsl local[1] (stack up the local instead, freed +1 byte)
10846 0x1c, // ne?
10847 0x31, 0x0c, // bnt 12d [after sal]
10848 //
10849 0x81, 0xd2, // lag global[210] (load into acc instead of stack)
10850 0x76, // push0 (push0 instead of ldi 0, freed +1 byte)
10851 0x22, // lt? (flip the comparison)
10852 0x31, 0x06, // bnt 6d [after sal]
10853 //
10854 0xe1, 0xd2, // -ag global[210]
10855 0x81, 0x58, // lag global[88]
10856 0xa3, 0x01, // sal local[1]
10857
10858 0x76, // push0 (0 call args)
10859 0x43, 0x2c, 0x00, // callk GameIsRestarting, 0d (add this to trigger our speed throttler)
10860 PATCH_END
10861 };
10862
10863 static const uint16 qfg3SignatureCombatSpeedThrottling2[] = {
10864 SIG_MAGICDWORD,
10865 SIG_UINT16(12), // priority 12
10866 SIG_UINT16(0x0000), // underbits 0
10867 SIG_UINT16(0x4010), // signal 4010h
10868 SIG_ADDTOOFFSET(+18),
10869 SIG_UINT16(0x0000), // scaleSignal 0
10870 SIG_UINT16(128), // scaleX 128
10871 SIG_UINT16(128), // scaleY 128
10872 SIG_UINT16(128), // maxScale 128
10873 SIG_UINT16(0x0000), // cycleSpeed 0
10874 SIG_END
10875 };
10876
10877 static const uint16 qfg3PatchCombatSpeedThrottling2[] = {
10878 PATCH_ADDTOOFFSET(+32),
10879 PATCH_UINT16(0x0005), // set cycleSpeed to 5
10880 PATCH_END
10881 };
10882
10883 // In room #750, when the hero enters from the top east path (room #755), it
10884 // could go out of the contained-access polygon bounds, and be able to travel
10885 // freely in the room.
10886 // The reason is that the cutoff y value (42) that determines whether the hero
10887 // enters from the top or bottom path is inaccurate: it's possible to enter the
10888 // top path from as low as y=45.
10889 // This patch changes the cutoff to be 50 which should be low enough.
10890 // It also changes the position in which the hero enters from the top east path
10891 // as the current location is hidden behind the tree.
10892 //
10893 // Applies to: English, French, German, Italian, Spanish and the GOG release.
10894 // Responsible method: enterEast::changeState (script 750)
10895 // Fixes bug: #6693
10896 static const uint16 qfg3SignatureRoom750Bounds1[] = {
10897 // (if (< (ego y?) 42)
10898 0x76, // push0 (y)
10899 0x76, // push0
10900 0x81, 0x00, // lag global[0] (ego)
10901 0x4a, 0x04, // send 4
10902 SIG_MAGICDWORD,
10903 0x36, // push
10904 0x35, 42, // ldi 42 (if ego.y < 42)
10905 0x22, // lt?
10906 SIG_END
10907 };
10908
10909 static const uint16 qfg3PatchRoom750Bounds1[] = {
10910 // (if (< (ego y?) 50)
10911 PATCH_ADDTOOFFSET(+8),
10912 50, // 50 (replace 42)
10913 PATCH_END
10914 };
10915
10916 static const uint16 qfg3SignatureRoom750Bounds2[] = {
10917 // (ego x: 294 y: 39)
10918 0x78, // push1 (x)
10919 0x78, // push1
10920 0x38, SIG_UINT16(294), // pushi 294
10921 0x76, // push0 (y)
10922 0x78, // push1
10923 SIG_MAGICDWORD,
10924 0x39, 0x1d, // pushi 29
10925 0x81, 0x00, // lag global[0] (ego)
10926 0x4a, 0x0c, // send 12
10927 SIG_END
10928 };
10929
10930 static const uint16 qfg3PatchRoom750Bounds2[] = {
10931 // (ego x: 320 y: 39)
10932 PATCH_ADDTOOFFSET(+3),
10933 PATCH_UINT16(320), // 320 (replace 294)
10934 PATCH_ADDTOOFFSET(+3),
10935 39, // 39 (replace 29)
10936 PATCH_END
10937 };
10938
10939 static const uint16 qfg3SignatureRoom750Bounds3[] = {
10940 // (ego setMotion: MoveTo 282 29 self)
10941 0x38, SIG_SELECTOR16(setMotion), // pushi setMotion (0x133)
10942 0x39, 0x04, // pushi 4
10943 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo
10944 0x36, // push
10945 0x38, SIG_UINT16(282), // pushi 282
10946 SIG_MAGICDWORD,
10947 0x39, 29, // pushi 29
10948 0x7c, // pushSelf
10949 0x81, 0x00, // lag global[0] (ego)
10950 0x4a, 0x0c, // send 12
10951 SIG_END
10952 };
10953
10954 static const uint16 qfg3PatchRoom750Bounds3[] = {
10955 // (ego setMotion: MoveTo 309 35 self)
10956 PATCH_ADDTOOFFSET(+9),
10957 PATCH_UINT16(309), // 309 (replace 282)
10958 PATCH_ADDTOOFFSET(+1),
10959 35, // 35 (replace 29)
10960 PATCH_END
10961 };
10962
10963 // When putting the last instance of an item in the chest in room 310 or 430,
10964 // holding the enter key causes a message to be sent to a non-object.
10965 //
10966 // This bug is similar to the Dag-Nab-It bug in QFG1VGA Mac. A script tries to
10967 // clear the inventory cursor by setting mainIconBar:curInvIcon to zero without
10968 // updating curIcon. This briefly places the icon bar in an illegal state which
10969 // causes mainIconBar:handleEvent to error when enter is pressed.
10970 //
10971 // We fix this by setting mainIconBar:curIcon to theWalkIcon to prevent the
10972 // illegal state where curInvIcon is zero while curIcon equals useIconItem.
10973 // useCode:init has two similar code paths with this bug.
10974 //
10975 // Applies to: All versions
10976 // Responsible method: useCode:init
10977 // Fixes bug: #11196
10978 static const uint16 qfg3SignatureChestIconBar[] = {
10979 0x38, SIG_SELECTOR16(say), // pushi say [ "You put it in the chest." ]
10980 0x39, 0x06, // pushi 06
10981 0x78, // push1
10982 0x39, 0x06, // pushi 06
10983 0x39, 0x04, // pushi 04
10984 0x78, // push1
10985 0x76, // push0
10986 0x39, 0x1d, // pushi 1d
10987 0x81, 0x5b, // lag 5b
10988 0x4a, 0x10, // send 10 [ qg3Messager say: 1 6 4 1 0 29 ]
10989 0x38, SIG_SELECTOR16(curInvIcon), // pushi curInvIcon
10990 0x78, // push1
10991 0x76, // push0
10992 0x81, 0x45, // lag 45
10993 SIG_ADDTOOFFSET(+0x8b),
10994 0x38, SIG_SELECTOR16(curInvIcon), // pushi curInvIcon
10995 0x78, // push1
10996 0x76, // push0
10997 0x81, 0x45, // lag 45
10998 0x4a, SIG_MAGICDWORD, 0x06, // send 06 [ mainIconBar curInvIcon: 0 ]
10999 0x38, SIG_SELECTOR16(say), // pushi say [ "You put it in the chest." ]
11000 SIG_END
11001 };
11002
11003 static const uint16 qfg3PatchChestIconBar[] = {
11004 0x39, PATCH_SELECTOR8(at), // pushi at
11005 0x78, // push1
11006 0x78, // push1
11007 0x81, 0x45, // lag 45
11008 0x4a, 0x06, // send 06 [ mainIconBar at: 1 ]
11009 0x38, PATCH_SELECTOR16(curInvIcon), // pushi curInvIcon
11010 0x78, // push1
11011 0x76, // push0
11012 0x38, PATCH_SELECTOR16(curIcon), // pushi curIcon
11013 0x78, // push1
11014 0x36, // push
11015 0x81, 0x45, // lag 45
11016 0x4a, 0x0c, // send 0c [ mainIconBar curInvIcon: 0 curIcon: theWalkIcon ]
11017 0x32, PATCH_UINT16(0x0094), // jmp 0094 [ "You put it in the chest." ]
11018 PATCH_ADDTOOFFSET(+0x8b),
11019 0x32, PATCH_UINT16(0xff59), // jmp ff59 [ mainIconBar curInvIcon: 0 curIcon: theWalkIcon ]
11020 PATCH_END
11021 };
11022
11023 // Entering the jungle close to the Leopardman village and then exiting with
11024 // Johari causes the game to display corrupt message boxes and divide by zero.
11025 // Manu's script has the same bugs which could potentially be triggered.
11026 //
11027 // The script fromJungle in room 170 sets local0 to the result of calculations
11028 // based on ego's initial distance to his destination. The scripts walkJohari
11029 // and walkManu divide by local0. fromJungle checks to see if it's set local0
11030 // to zero, but instead of addressing this, it displays a series of messages
11031 // all at once and corrupts the screen before continuing on to the error.
11032 //
11033 // We fix this by not setting local0 to zero and disabling the broken messages.
11034 // The message code is unnecessary since later in the same game cycle a local
11035 // procedure displays the messages correctly. This is similar to the fix in the
11036 // QFG3 Unofficial Update: https://github.com/AshLancer/QFG3-Fan-Patch
11037 //
11038 // Applies to: All versions
11039 // Responsible method: fromJungle:changeState(0)
11040 // Fixes bug: #11216
11041 static const uint16 qfg3SignatureJohariManuMapBugs[] = {
11042 SIG_MAGICDWORD,
11043 0xa3, 0x00, // sal 00 [ local0 = result ]
11044 0x36, // push
11045 0x35, 0x01, // ldi 01
11046 0x22, // lt? [ local0 < 1 ]
11047 0x31, // bnt [ skip broken messages ]
11048 SIG_END
11049 };
11050
11051 static const uint16 qfg3PatchJohariManuMapBugs[] = {
11052 0x2f, 0x02, // bt 02
11053 0x35, 0x01, // ldi 01
11054 0xa3, 0x00, // sal 00 [ local0 = result ? result : 1 ]
11055 0x33, // jmp [ always skip broken messages ]
11056 PATCH_END
11057 };
11058
11059 // script, description, signature patch
11060 static const SciScriptPatcherEntry qfg3Signatures[] = {
11061 { true, 944, "import dialog continuous calls", 1, qfg3SignatureImportDialog, qfg3PatchImportDialog },
11062 { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialog, qfg3PatchWooDialog },
11063 { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialogAlt, qfg3PatchWooDialogAlt },
11064 { true, 52, "export character save bug", 2, qfg3SignatureExportChar, qfg3PatchExportChar },
11065 { true, 54, "import character from QfG1 bug", 1, qfg3SignatureImportQfG1Char, qfg3PatchImportQfG1Char },
11066 { true, 640, "chief in hut priority fix", 1, qfg3SignatureChiefPriority, qfg3PatchChiefPriority },
11067 { true, 285, "missing points for telling about initiation heap", 1, qfg3SignatureMissingPoints1, qfg3PatchMissingPoints1 },
11068 { true, 285, "missing points for telling about initiation script", 1, qfg3SignatureMissingPoints2a, qfg3PatchMissingPoints2 },
11069 { true, 285, "missing points for telling about initiation script", 1, qfg3SignatureMissingPoints2b, qfg3PatchMissingPoints2 },
11070 { true, 550, "combat speed throttling script", 1, qfg3SignatureCombatSpeedThrottling1, qfg3PatchCombatSpeedThrottling1 },
11071 { true, 550, "combat speed throttling heap", 1, qfg3SignatureCombatSpeedThrottling2, qfg3PatchCombatSpeedThrottling2 },
11072 { true, 750, "hero goes out of bounds in room 750", 2, qfg3SignatureRoom750Bounds1, qfg3PatchRoom750Bounds1 },
11073 { true, 750, "hero goes out of bounds in room 750", 2, qfg3SignatureRoom750Bounds2, qfg3PatchRoom750Bounds2 },
11074 { true, 750, "hero goes out of bounds in room 750", 2, qfg3SignatureRoom750Bounds3, qfg3PatchRoom750Bounds3 },
11075 { true, 29, "icon bar crash when using chest", 1, qfg3SignatureChestIconBar, qfg3PatchChestIconBar },
11076 { true, 170, "johari/manu map crash and message bugs", 2, qfg3SignatureJohariManuMapBugs, qfg3PatchJohariManuMapBugs },
11077 SCI_SIGNATUREENTRY_TERMINATOR
11078 };
11079
11080 #ifdef ENABLE_SCI32
11081 #pragma mark -
11082 #pragma mark Quest for Glory 4
11083
11084 // ===========================================================================
11085 // Cranium's TRAP screen in room 380 incorrectly creates an int array for
11086 // string data.
11087 //
11088 // Applies to at least: English CD, English floppy, German floppy
11089 // Responsible method: trap::init() in script 83
11090 // Fixes bug: #10766
11091 static const uint16 qfg4TrapArrayTypeSignature[] = {
11092 0x38, SIG_SELECTOR16(new), // pushi new
11093 0x78, // push1
11094 SIG_MAGICDWORD,
11095 0x38, SIG_UINT16(0x0080), // pushi 128d (array size)
11096 0x51, SIG_ADDTOOFFSET(+1), // class IntArray (CD=0x0b. floppy=0x0a)
11097 0x4a, SIG_UINT16(0x0006), // send 6
11098 SIG_END
11099 };
11100
11101 static const uint16 qfg4TrapArrayTypePatch[] = {
11102 PATCH_ADDTOOFFSET(+4),
11103 0x38, PATCH_UINT16(0x0100), // pushi 256d (array size)
11104 0x51, PATCH_GETORIGINALBYTEADJUST(8, +2), // class ByteArray (CD=0x0d, floppy=0x0c)
11105 PATCH_END
11106 };
11107
11108 // QFG4 has custom video benchmarking code inside a subroutine, which is called
11109 // by 'glryInit::init', that needs to be disabled. See: sci2BenchmarkSignature.
11110 //
11111 // Applies to at least: English CD, English floppy, German Floppy
11112 // Responsible method: localproc_0010() in script 1
11113 static const uint16 qfg4BenchmarkSignature[] = {
11114 0x38, SIG_SELECTOR16(new), // pushi new
11115 0x76, // push0
11116 0x51, SIG_ADDTOOFFSET(+1), // class View
11117 0x4a, SIG_UINT16(0x0004), // send 4
11118 0xa5, 0x00, // sat temp[0]
11119 0x39, SIG_SELECTOR8(view), // pushi view
11120 SIG_MAGICDWORD,
11121 0x78, // push1
11122 0x38, SIG_UINT16(0x270f), // pushi $270f (9999)
11123 SIG_END
11124 };
11125
11126 static const uint16 qfg4BenchmarkPatch[] = {
11127 0x35, 0x01, // ldi 1
11128 0xa1, 0xbf, // sag global[191]
11129 0x48, // ret
11130 PATCH_END
11131 };
11132
11133 // WORKAROUND: Script needed, because of differences in our pathfinding
11134 // algorithm.
11135 // At the inn, there is a path that goes off screen. In our pathfinding
11136 // algorithm, we move all the pathfinding points so that they are within
11137 // the visible area. However, two points of the path are outside the
11138 // screen, so moving them will place them both on top of each other,
11139 // thus creating an impossible pathfinding area. This makes the
11140 // pathfinding algorithm ignore the walkable area when the hero moves
11141 // up the ladder to his room. We therefore move one of the points
11142 // slightly, so that it is already within the visible screen, so that
11143 // the walkable polygon is valid, and the pathfinding algorithm can
11144 // work properly.
11145 //
11146 // Applies to at least: English CD, English floppy, German floppy
11147 // Responsible method: rm320::init() in script 320
11148 // Fixes bug: #10693
11149 static const uint16 qfg4InnPathfindingSignature[] = {
11150 SIG_MAGICDWORD,
11151 0x38, SIG_UINT16(0x0154), // pushi x = 340
11152 0x39, 0x77, // pushi y = 119
11153 0x38, SIG_UINT16(0x0114), // pushi x = 276
11154 0x39, 0x31, // pushi y = 49
11155 0x38, SIG_UINT16(0x00fc), // pushi x = 252
11156 0x39, 0x30, // pushi y = 48
11157 0x38, SIG_UINT16(0x00a5), // pushi x = 165
11158 0x39, 0x55, // pushi y = 85
11159 0x38, SIG_UINT16(0x00c0), // pushi x = 192
11160 0x39, 0x55, // pushi y = 85
11161 0x38, SIG_UINT16(0x010b), // pushi x = 267
11162 0x39, 0x34, // pushi y = 52
11163 0x38, SIG_UINT16(0x0144), // pushi x = 324
11164 0x39, 0x77, // pushi y = 119
11165 SIG_END
11166 };
11167
11168 static const uint16 qfg4InnPathfindingPatch[] = {
11169 PATCH_ADDTOOFFSET(+30),
11170 0x38, PATCH_UINT16(0x013f), // pushi x = 319 (was 324)
11171 0x39, 0x77, // pushi y = 119
11172 PATCH_END
11173 };
11174
11175 // When autosave is enabled, Glory::save() (script 0) deletes savegame files in
11176 // a loop, while disk space is insufficient for a new save, or while there are
11177 // 20+ saves. Since ScummVM handles slots differently and allows far more
11178 // slots, this deletes all but the most recent 19 manual saves, merely by
11179 // walking from room to room!
11180 //
11181 // Ironically, kGetSaveFiles() (kfile.cpp) and the debugger's 'list_files'
11182 // command rely on listSavegames() (file.cpp), which specifically omits the
11183 // autosave slot, so the script will only ever delete manual saves. And the
11184 // space check doesn't take into account the reduced demand when overwriting an
11185 // existing autosave.
11186 //
11187 // No good can come of this loop. So we skip it entirely. If the disk truly is
11188 // out of space, a message box will complain, and the player can delete saves
11189 // voluntarily.
11190 //
11191 // Note: Glory::save() contains another space freeing loop, but it might be
11192 // unreachable.
11193 //
11194 // Applies to at least: English CD, English floppy, German floppy
11195 // Responsible method: Glory::save() in script 0
11196 // Fixes bug: #10758
11197 static const uint16 qfg4AutosaveSignature[] = {
11198 0x30, SIG_ADDTOOFFSET(+2), // bnt ?? [end the loop]
11199 0x78, // push1 (1 call arg)
11200 //
11201 0x39, SIG_SELECTOR8(data), // pushi data
11202 0x76, // push0
11203 SIG_ADDTOOFFSET(+2), // (CD="lag global[29]", floppy="lat temp[6]")
11204 0x4a, SIG_UINT16(0x0004), // send 4d
11205 0x36, // push
11206 SIG_MAGICDWORD,
11207 0x43, 0x3f, SIG_UINT16(0x0002), // callk CheckFreeSpace, 2d
11208 0x18, // not
11209 0x2f, 0x05, // bt 05 [skip other OR condition]
11210 0x8d, 0x09, // lst temp[9] (savegame file count)
11211 0x35, 0x14, // ldi 20d
11212 0x20, // ge?
11213 SIG_END
11214 };
11215
11216 static const uint16 qfg4AutosavePatch[] = {
11217 0x32, // ... // jmp [end the loop]
11218 PATCH_END
11219 };
11220
11221 // The swamp areas have typos where a Grooper object is passed to
11222 // View::setLoop(), a method which expects an integer to store in the "loop"
11223 // property. This leads to arithmetic crashes later. We change it to
11224 // Actor::setLooper().
11225 //
11226 // Applies to at least: English CD, English floppy, German floppy
11227 // Responsible method:
11228 // Script 440
11229 // sToWater::changeState(3)
11230 // Script 530, 535
11231 // sGlideFromTuff::changeState(1)
11232 // sGoGlide::changeState(2)
11233 // sFromWest::changeState(0)
11234 // sFromSouth::changeState(0)
11235 // Script 541, 542, 543
11236 // sGlideFromTuff::changeState(1)
11237 // sFromEast::changeState(0)
11238 // sFromNorth::changeState(0)
11239 // sFromWest::changeState(0)
11240 // sFromSouth::changeState(0)
11241 // Script 545
11242 // sCombatEnter::changeState(0)
11243 // sGlideFromTuff::changeState(2)
11244 // sFromNorth::changeState(0)
11245 // sFromEast::changeState(0)
11246 // sFromWest::changeState(0)
11247 // Fixes bug: #10777
11248 static const uint16 qfg4SetLooperSignature1[] = {
11249 SIG_MAGICDWORD,
11250 0x38, SIG_SELECTOR16(setLoop), // pushi setLoop
11251 0x78, // push1
11252 0x51, 0x5a, // class Grooper
11253 SIG_END
11254 };
11255
11256 static const uint16 qfg4SetLooperPatch1[] = {
11257 0x38, PATCH_SELECTOR16(setLooper), // pushi setLooper
11258 PATCH_END
11259 };
11260
11261 // As above, except it's an exported subclass of Grooper: stopGroop.
11262 //
11263 // Applies to at least: English CD, English floppy, German floppy
11264 // Responsible method: sJumpWater::changeState(3), sToJump::changeState(2) in script 10
11265 // Fixes bug: #10777
11266 static const uint16 qfg4SetLooperSignature2[] = {
11267 SIG_MAGICDWORD,
11268 0x38, SIG_SELECTOR16(setLoop), // pushi setLoop
11269 0x78, // push1
11270 0x7a, // push2 (2 call args)
11271 0x39, 0x1c, // pushi 28d
11272 0x78, // push1
11273 0x43, 0x02, SIG_UINT16(0x0004), // callk ScriptID, 4d (ScriptID 28 1)
11274 SIG_END
11275 };
11276
11277 static const uint16 qfg4SetLooperPatch2[] = {
11278 0x38, PATCH_SELECTOR16(setLooper), // pushi setLooper
11279 PATCH_END
11280 };
11281
11282 // The panel showing time of day gets stuck displaying consecutive moonrises.
11283 // Despite time actually advancing, the sun will never rise again because
11284 // local[5] is set to 1 for night UI and never resets until hero moves to
11285 // another room.
11286 //
11287 // temp[0] is not used in this method. Thus we can safely ignore and reuse the
11288 // code block that manipulates it in order to fix this bug.
11289 //
11290 // Applies to at least: English CD, English floppy, German floppy
11291 // Responsible method: showTime::init() in script 7
11292 // Fixes bug: #10775
11293 static const uint16 qfg4MoonriseSignature[] = {
11294 SIG_MAGICDWORD,
11295 0x81, 0x7a, // lag global[122]
11296 0xa5, 0x00, // sat temp[0]
11297 0x89, 0x7b, // lsg global[123]
11298 0x35, 0x06, // ldi 6d
11299 0x1c, // ne?
11300 0x2f, 0x06, // bt 6d [skip remaining OR conditions]
11301 0x89, 0x78, // lsg global[120]
11302 0x34, SIG_UINT16(0x01f4), // ldi 500d
11303 0x1e, // gt?
11304 0x31, 0x02, // bnt 2d [skip the if block]
11305 0xc5, 0x00, // +at temp[0]
11306 SIG_END
11307 };
11308
11309 static const uint16 qfg4MoonrisePatch[] = {
11310 0x35, 0x00, // ldi 0 (reset the is-night var)
11311 0xa3, 0x05, // sal local[5]
11312 0x33, 0x0f, // jmp 15d [skip waste bytes]
11313 PATCH_END
11314 };
11315
11316 // Visiting the inn after rescuing Igor sets a plot flag. Such flags are tested
11317 // on subsequent visits to decide the dialogue options when clicking MOUTH on
11318 // hero. That particular check neglects time of day, allowing hero to talk to
11319 // an empty room after midnight... and get responses from the absent innkeeper.
11320 //
11321 // The inn's init() has a series of cond blocks to boil down all the checks
11322 // into values for local[2], representing discrete situations. Then there's a
11323 // switch block in sInitShit() that acts on those values, making arrival
11324 // announcements and setting new flags.
11325 //
11326 // "So Dmitri says the gypsy didn't really kill Igor after all." sets flag 132.
11327 // "I must thank you for saving our Tanya." sets flag 134.
11328 //
11329 // There are two bugged situations. When you have flag 132 and haven't gotten
11330 // 134 yet, local[2] = 11. When you get flag 134, local[2] = 12. Neither
11331 // of them consider the time of day, talking as if the innkeeper were always
11332 // present.
11333 //
11334 // A day in QFG4 is broken up into 3-hour spans: 6,7,0,1,2,3,4,5. Where 6 is
11335 // midnight. The sun rises in 0 and sets in 4. Current span is global[123]. The
11336 // innkeeper sprite is not around from midnight to morning.
11337 //
11338 // To make room, we optimize block 10's time check:
11339 // "t <= 3 || t is in [4, 5]" becomes "t <= 5". No need for a lengthy call
11340 // to do a simple comparison. Conceptually it meant, "daytime and evening".
11341 //
11342 // That gap is used to insert similar pre-midnight checks before block 11 and
11343 // block 12. This will sync them with the sprite's schedule. Ideally, the
11344 // sprite never would've been scheduled separately in the first place.
11345 //
11346 // Applies to at least: English CD, English floppy, German floppy
11347 // Responsible method: rm320::init() in script 320
11348 // Fixes bug: #10753
11349 static const uint16 qfg4AbsentInnkeeperSignature[] = {
11350 SIG_MAGICDWORD, // (block 10, partway through)
11351 0x31, 0x1c, // bnt 28d [block 11]
11352 0x89, 0x7b, // lsg global[123]
11353 0x35, 0x03, // ldi 3d
11354 0x24, // le?
11355 // (~~ junk begins ~~)
11356 0x2f, 0x0f, // bt 15d [after the calle]
11357 0x39, 0x03, // pushi 3d (3 call args)
11358 0x89, 0x7b, // lsg global[123] (needle value)
11359 0x39, 0x04, // pushi 4d (haystack values...)
11360 0x39, 0x05, // pushi 5d
11361 0x46, SIG_UINT16(0xfde7), SIG_UINT16(0x0005), SIG_UINT16(0x0006), // calle [export 5 of script 64999], 6d (is needle in haystack?))
11362 // (~~ junk ends ~~)
11363 0x31, 0x04, // bnt 4d [block 11]
11364 0x35, 0x0a, // ldi 10d
11365 0x33, 0x29, // jmp 41d [done, local[2]=acc]
11366 //
11367 SIG_ADDTOOFFSET(+25), // (...block 11...) (patch ends after this)
11368 SIG_ADDTOOFFSET(+14), // (...block 12...)
11369 SIG_ADDTOOFFSET(+2), // (...else 0...)
11370 0xa3, 0x02, // sal local[2] (all blocks set acc and jmp here)
11371 SIG_END
11372 };
11373
11374 static const uint16 qfg4AbsentInnkeeperPatch[] = {
11375 0x31, 0x0e, // bnt 14d [block 11]
11376 0x89, 0x7b, // lsg global[123]
11377 0x35, 0x05, // ldi 5d (make it t <= 5)
11378 0x24, // le?
11379 // (*snip*)
11380 0x31, 0x07, // bnt 7d [block 11]
11381 0x35, 0x0a, // ldi 10d
11382 0x33, 0x3a, // jmp 58d [done, local[2]=acc]
11383 //
11384 0x34, PATCH_UINT16(0x0000), // ldi 0 (waste 3 bytes)
11385 // (14 freed bytes remain.)
11386 // (use 7 freed bytes to prepend block 11)
11387 // (and shift all of block 11 up by 7 bytes)
11388 // (use that new gap below to prepend block 12)
11389 //
11390 // (block 11, prepend a time check)
11391 0x89, 0x7b, // lsg global[123]
11392 0x35, 0x05, // ldi 5d
11393 0x24, // le?
11394 0x31, 0x19, // bnt 25d [block 12]
11395 // (block 11, original ops shift up)
11396 0x78, // push1 (1 call arg)
11397 0x38, PATCH_UINT16(0x0084), // pushi 132d
11398 0x45, 0x04, PATCH_UINT16(0x0002), // callb [export 4 of script 0], 2d (test flag 132)
11399 0x31, 0x0f, // bnt 15d [next block]
11400 0x78, // push1 (1 call arg)
11401 0x38, PATCH_UINT16(0x0086), // pushi 134d
11402 0x45, 0x04, PATCH_UINT16(0x0002), // callb [export 4 of script 0], 2d (test flag 134)
11403 0x18, // not
11404 0x31, 0x04, // bnt 4d [block 12]
11405 0x35, 0x0b, // ldi 11d
11406 0x33, 0x17, // jmp 23d [done, local[2]=acc]
11407 //
11408 // (block 12, prepend a time check)
11409 0x89, 0x7b, // lsg global[123]
11410 0x35, 0x05, // ldi 5d
11411 0x24, // le?
11412 0x31, 0x0e, // bnt 14d [else block]
11413 // (block 12, original ops continue here)
11414 PATCH_END
11415 };
11416
11417 // WORKAROUND: Script needed, because of differences in our pathfinding
11418 // algorithm.
11419 // When entering flipped stairways from the upper door, hero is initially
11420 // placed outside the walkable area. As a result, hero will float around
11421 // inappropriately in stairways leading to Tanya's room (620) and to the iron
11422 // safe (624).
11423 //
11424 // The polygon's first and final points are the top of the stairs. It's quite
11425 // narrow up there, and the final segment doesn't trace the wall very well. We
11426 // move the final point down and over to round out the path. Point 0 takes 19's
11427 // original place. Point 1 takes 0's original place.
11428 //
11429 // Disregard the responsible method's misleading name.
11430 //
11431 // Applies to at least: English CD, English floppy, German floppy
11432 // Responsible method: rm620Code::init() in script 633
11433 // Fixes bug: #10757
11434 static const uint16 qfg4StairwayPathfindingSignature[] = {
11435 SIG_MAGICDWORD,
11436 0x38, SIG_UINT16(0x00e2), // pushi 226d (point 0 is top-left)
11437 0x39, 0x20, // pushi 32d
11438 0x38, SIG_UINT16(0x00ed), // pushi 237d (point 1 is below on left)
11439 0x39, 0x26, // pushi 38d
11440 SIG_ADDTOOFFSET(+87), // ...
11441 0x38, SIG_UINT16(0x00e9), // pushi 233d (point 19 is top-right)
11442 0x39, 0x20, // pushi 32d
11443 SIG_END
11444 };
11445
11446 static const uint16 qfg4StairwayPathfindingPatch[] = {
11447 0x38, PATCH_UINT16(0x00e9), // pushi 233d (point 0 gets 19's coords)
11448 0x39, 0x20, // pushi 32d
11449 0x38, PATCH_UINT16(0x00e2), // pushi 226d (point 1 gets 0's coords)
11450 0x39, 0x20, // pushi 32d
11451 PATCH_ADDTOOFFSET(+87), // ...
11452 0x38, PATCH_UINT16(0x00fd), // pushi 253d (point 19 hugs the wall)
11453 0x39, 0x2b, // pushi 43d
11454 PATCH_END
11455 };
11456
11457 // Whenever levitate is cast, a cryptic error message appears in-game.
11458 // "<Prop setScale:> y value less than vanishingY"
11459 //
11460 // There are typos where hero is passed to Prop::setScale(), a method which
11461 // expects integers. We change it to Prop::setScaler().
11462 //
11463 // Applies to at least: English CD, English floppy, German floppy
11464 // Responsible method: sLevitate::changeState(3) in script 31
11465 // sLevitating::changeState(0) in script 800 (CD only)
11466 // Fixes bug: #10726
11467 static const uint16 qfg4SetScalerSignature[] = {
11468 SIG_MAGICDWORD,
11469 0x38, SIG_SELECTOR16(setScale), // pushi setScale
11470 0x78, // push1
11471 0x89, 0x00, // lsg global[0] (hero)
11472 SIG_END
11473 };
11474
11475 static const uint16 qfg4SetScalerPatch[] = {
11476 0x38, PATCH_SELECTOR16(setScaler), // pushi setScaler
11477 PATCH_END
11478 };
11479
11480 // The castle's crest-operated bookshelf has an unconditional HAND message
11481 // which always says, "you haven't found the trigger yet," even after it's
11482 // open.
11483 //
11484 // We schedule the walk-out script (sLeaveSecretly) at the end of the opening
11485 // script (sSecret) to force hero to leave immediately, preventing any
11486 // interaction with an open bookshelf.
11487 //
11488 // An automatic exit is consistent with the other bookshelf passage rooms:
11489 // Chandelier (662) and EXIT (661).
11490 //
11491 // Clobbers Glory::handsOn() and sSecret::dispose() to do Room::setScript().
11492 // Both of them are made redundant by setScript's built-in disposal and
11493 // sLeaveSecretly's immediate use of Glory::handsOff().
11494 //
11495 // This patch has two variants, toggled to match the detected edition with
11496 // enablePatch() below. Aside from the patched lofsa value, they are identical.
11497 //
11498 // Applies to at least: English CD
11499 // Responsible method: sSecret::changeState(4) in script 663
11500 // Fixes bug: #10756
11501 static const uint16 qfg4CrestBookshelfCDSignature[] = {
11502 SIG_MAGICDWORD,
11503 0x4a, SIG_UINT16(0x003e), // send 62d
11504 0x36, // push
11505 SIG_ADDTOOFFSET(+5), // ...
11506 0x38, SIG_SELECTOR16(handsOn), // pushi handsOn (begin clobbering)
11507 0x76, // push0
11508 0x81, 0x01, // lag global[1] (Glory)
11509 0x4a, SIG_UINT16(0x0004), // send 4d
11510 0x38, SIG_SELECTOR16(dispose), // pushi dispose
11511 0x76, // push0
11512 0x54, SIG_UINT16(0x0004), // self 4d
11513 SIG_END
11514 };
11515
11516 static const uint16 qfg4CrestBookshelfCDPatch[] = {
11517 PATCH_ADDTOOFFSET(+9),
11518 0x38, PATCH_SELECTOR16(setScript), // pushi setScript
11519 0x78, // push1
11520 0x72, PATCH_UINT16(0x01a4), // lofsa sLeaveSecretly
11521 0x36, // push
11522 0x81, 0x02, // lag global[2] (rm663)
11523 0x4a, PATCH_UINT16(0x0006), // send 6d
11524 0x34, PATCH_UINT16(0x0000), // ldi 0 (waste 3 bytes)
11525 PATCH_END
11526 };
11527
11528 // Applies to at least: English floppy, German floppy
11529 static const uint16 qfg4CrestBookshelfFloppySignature[] = {
11530 SIG_MAGICDWORD,
11531 0x4a, SIG_UINT16(0x003e), // send 62d
11532 0x36, // push
11533 SIG_ADDTOOFFSET(+5), // ...
11534 0x38, SIG_SELECTOR16(handsOn), // pushi handsOn (begin clobbering)
11535 0x76, // push0
11536 0x81, 0x01, // lag global[1] (Glory)
11537 0x4a, SIG_UINT16(0x0004), // send 4d
11538 0x38, SIG_SELECTOR16(dispose), // pushi dispose
11539 0x76, // push0
11540 0x54, SIG_UINT16(0x0004), // self 4d
11541 SIG_END
11542 };
11543
11544 static const uint16 qfg4CrestBookshelfFloppyPatch[] = {
11545 PATCH_ADDTOOFFSET(+9),
11546 0x38, PATCH_SELECTOR16(setScript), // pushi setScript
11547 0x78, // push1
11548 0x72, PATCH_UINT16(0x018c), // lofsa sLeaveSecretly
11549 0x36, // push
11550 0x81, 0x02, // lag global[2] (rm663)
11551 0x4a, PATCH_UINT16(0x0006), // send 6d
11552 0x34, PATCH_UINT16(0x0000), // ldi 0 (waste 3 bytes)
11553 PATCH_END
11554 };
11555
11556 // Modifies room 663's sLeaveSecretly to avoid obstacles.
11557 //
11558 // Originally intended to start when hero arrives at a doorMat region, room
11559 // 663's walk-out script (sLeaveSecretly) ignores obstacles. The crest
11560 // bookshelf patch repurposes this script and requires collision detection to
11561 // exit properly, walking around the open bookshelf.
11562 //
11563 // Class numbers for MoveTo and PolyPath differ between CD vs floppy editions.
11564 // Their intervals happen to be the same, so we simply offset whatever is
11565 // there.
11566 //
11567 // Applies to at least: English CD, English floppy, German floppy
11568 // Responsible method: sLeaveSecretly::changeState(1) in script 663
11569 // Fixes bug: #10756
11570 static const uint16 qfg4CrestBookshelfMotionSignature[] = {
11571 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo
11572 0x36, // push
11573 SIG_MAGICDWORD,
11574 0x39, 0x1d, // pushi x = 29d
11575 0x38, SIG_UINT16(0x0097), // pushi y = 151d
11576 SIG_END
11577 };
11578
11579 static const uint16 qfg4CrestBookshelfMotionPatch[] = {
11580 0x51, PATCH_GETORIGINALBYTEADJUST(+1, +6), // class PolyPath
11581 PATCH_END
11582 };
11583
11584 // In the crest bookshelf room (663) connected to the upper door of the
11585 // bat-infested stairway, peering through the keyhole *always* reports bats.
11586 //
11587 // As you kill the bats, global plot flags are set. Normally flags 331-334
11588 // would be checked via proc0_4(). They're in a bitmask, among other flags, so
11589 // we can check all simultaneously (0000000000011110).
11590 //
11591 // global[520] & 30 == 30
11592 //
11593 // Patch 1: There was no space for this in the responsible method. Instead, we
11594 // rewrite a different method that became obsolete after the crest bookshelf
11595 // patch: sCloseSecretDoor::changeState().
11596 //
11597 // Patch 2: We modify sPeepingTom to call our rewritten sCloseSecretDoor. This
11598 // has two variants, toggled to match the detected edition with enablePatch()
11599 // below. Aside from the patched lofsa value, they are identical.
11600 //
11601 // Requires patch: qfg4CrestBookshelf (CD or Floppy)
11602 // Applies to at least: English CD, English floppy, German floppy
11603 // Responsible method: sPeepingTom::changeState(1) in script 663
11604 // Fixes bug: #10789
11605 static const uint16 qfg4UpperPeerBatsSignature1[] = {
11606 0x87, 0x01, // lap param[1]
11607 SIG_ADDTOOFFSET(+41), // ...
11608 SIG_MAGICDWORD,
11609 0x4a, SIG_UINT16(0x0006), // send 6d
11610 0x35, 0x1e, // ldi 30d
11611 0x65, SIG_ADDTOOFFSET(+1), // aTop ticks
11612 SIG_ADDTOOFFSET(+9), // ...
11613 0x38, SIG_SELECTOR16(setCycle), // pushi setCycle
11614 SIG_END
11615 };
11616
11617 static const uint16 qfg4UpperPeerBatsPatch1[] = {
11618 0x38, PATCH_SELECTOR16(say), // pushi say (decide the message as args are stacked up)
11619 0x39, 0x06, // pushi 6d
11620 0x7a, // push2
11621 0x38, PATCH_UINT16(0x009b), // pushi 155d
11622
11623 0x39, 0x1e, // pushi 30d (stack up for eq)
11624 0x3c, // dup (stack up another for AND)
11625 0x80, PATCH_UINT16(0x0208), // lag global[520] (plot flags bitmask)
11626 0x12, // and
11627 0x1a, // eq? (Were all dead bat flags set?)
11628 0x2f, 0x04, // bt 4d [after this jmp]
11629 0x39, 0x1d, // pushi 29d (bat message)
11630 0x33, 0x02, // jmp 2d [after deciding message]
11631
11632 0x39, 0x1b, // pushi 27d (killed all bats, generic message)
11633
11634 0x78, // push1
11635 0x76, // push0 (don't cue() afterward)
11636 0x38, PATCH_UINT16(0x0280), // pushi 640d
11637 0x81, 0x5b, // lag global[91] (gloryMessager)
11638 0x4a, PATCH_UINT16(0x0010), // send 16d
11639 0x48, // ret
11640 0x34, PATCH_UINT16(0x0000), // ldi 0 (erase 3 bytes to keep disasm aligned)
11641 PATCH_END
11642 };
11643
11644 // Applies to at least: English CD
11645 static const uint16 qfg4UpperPeerBatsCDSignature2[] = {
11646 0x38, SIG_SELECTOR16(say), // pushi say
11647 SIG_ADDTOOFFSET(+3),
11648 SIG_MAGICDWORD,
11649 0x7a, // push2
11650 0x38, SIG_UINT16(0x009b), // pushi 155d
11651 0x39, 0x1d, // pushi 29d (bat message)
11652 SIG_ADDTOOFFSET(+7),
11653 0x4a, SIG_UINT16(0x0010), // send 16d (say: 2 155 29 1 self 640)
11654 PATCH_END
11655 };
11656
11657 static const uint16 qfg4UpperPeerBatsCDPatch2[] = {
11658 0x38, PATCH_SELECTOR16(changeState), // pushi changeState
11659 0x78, // push1
11660 0x76, // push0
11661 0x72, PATCH_UINT16(0x0176), // lofsa sCloseSecretDoor
11662 0x4a, PATCH_UINT16(0x0006), // send 6d (call the rewritten method)
11663 0x38, PATCH_SELECTOR16(cue), // pushi cue
11664 0x76, // push0
11665 0x54, PATCH_UINT16(0x0004), // self 4d (self-cue)
11666 0x35, 0x00, // ldi 0 (waste 2 bytes)
11667 0x35, 0x00, // ldi 0 (waste 2 bytes)
11668 PATCH_END
11669 };
11670
11671 // Applies to at least: English floppy, German floppy
11672 static const uint16 qfg4UpperPeerBatsFloppySignature2[] = {
11673 0x38, SIG_SELECTOR16(say), // pushi say
11674 SIG_ADDTOOFFSET(+3),
11675 SIG_MAGICDWORD,
11676 0x7a, // push2
11677 0x38, SIG_UINT16(0x009b), // pushi 155d
11678 0x39, 0x1d, // pushi 29d (bat message)
11679 SIG_ADDTOOFFSET(+7),
11680 0x4a, SIG_UINT16(0x0010), // send 16d (say: 2 155 29 1 self 640)
11681 PATCH_END
11682 };
11683
11684 static const uint16 qfg4UpperPeerBatsFloppyPatch2[] = {
11685 0x38, PATCH_SELECTOR16(changeState), // pushi changeState
11686 0x78, // push1
11687 0x76, // push0
11688 0x72, PATCH_UINT16(0x0160), // lofsa sCloseSecretDoor
11689 0x4a, PATCH_UINT16(0x0006), // send 6d (call the rewritten method)
11690 0x38, PATCH_SELECTOR16(cue), // pushi cue
11691 0x76, // push0
11692 0x54, PATCH_UINT16(0x0004), // self 4d (self-cue)
11693 0x35, 0x00, // ldi 0 (waste 2 bytes)
11694 0x35, 0x00, // ldi 0 (waste 2 bytes)
11695 PATCH_END
11696 };
11697
11698 // In the room (644) connected to the lower door of the bat-infested stairway,
11699 // peering through the keyhole *always* reports bats.
11700 //
11701 // As you kill the bats, global plot flags are set. Normally flags 331-334
11702 // would be checked via proc0_4(). They're in a bitmask, among other flags, so
11703 // we can check all simultaneously (0000000000011110).
11704 //
11705 // global[520] & 30 == 30
11706 //
11707 // Room 644 has an IF-ELSE deciding between largely redundant calls to
11708 // gloryMessager::say(). We make room by combining them.
11709 //
11710 // Applies to at least: English CD, English floppy, German floppy
11711 // Responsible method: sPeepingTom::changeState(1) in script 644
11712 // Fixes bug: #10789
11713 static const uint16 qfg4LowerPeerBatsSignature[] = {
11714 SIG_MAGICDWORD,
11715 0x78, // push1 x (check if hero's near the left door)
11716 0x76, // push0
11717 0x81, 0x00, // lag global[0] (hero)
11718 0x4a, SIG_UINT16(0x0004), // send 4d
11719 0x36, // push
11720 0x35, 0x3c, // ldi 60d
11721 0x22, // lt?
11722 0x31, 0x18, // bnt 24d [else right door w/ bats]
11723 0x38, SIG_SELECTOR16(say), // pushi say (left door, generic message)
11724 SIG_ADDTOOFFSET(+14), // ...
11725 0x81, 0x5b, // lag global[91] (gloryMessager)
11726 0x4a, SIG_UINT16(0x0010), // send 16d (say: 2 155 27 1 self 640)
11727 0x33, SIG_ADDTOOFFSET(+1), // jmp [end the case]
11728 SIG_ADDTOOFFSET(+22), // (right door, say(), 3rd arg is 29 for bat message)
11729 0x33, SIG_ADDTOOFFSET(+1), // jmp [end the case]
11730 SIG_END
11731 };
11732
11733 static const uint16 qfg4LowerPeerBatsPatch[] = {
11734 0x38, PATCH_SELECTOR16(say), // pushi say (decide the message as args are stacked up)
11735 0x39, 0x06, // pushi 6d
11736 0x7a, // push2
11737 0x38, PATCH_UINT16(0x009b), // pushi 155d
11738
11739 0x78, // push1 x (check if left door)
11740 0x76, // push0
11741 0x81, 0x00, // lag global[0] (hero)
11742 0x4a, PATCH_UINT16(0x0004), // send 4d
11743 0x36, // push
11744 0x35, 0x3c, // ldi 60d
11745 0x22, // lt?
11746 0x31, 0x04, // bnt 4d [after this jmp]
11747 0x39, 0x1b, // pushi 27d (left door, generic message)
11748 0x33, 0x10, // jmp 16d [after deciding message]
11749
11750 0x39, 0x1e, // pushi 30d (stack up for eq)
11751 0x3c, // dup (stack up another for AND)
11752 0x80, PATCH_UINT16(0x0208), // lag global[520] (plot flags bitmask)
11753 0x12, // and
11754 0x1a, // eq? (Were all dead bat flags set?)
11755 0x2f, 0x04, // bt 4d [after this jmp]
11756 0x39, 0x1d, // pushi 29d (right door, bat message)
11757 0x33, 0x02, // jmp 2d [after deciding message]
11758
11759 0x39, 0x1b, // pushi 27d (right door, killed all bats, generic message)
11760
11761 0x78, // push1
11762 0x7c, // pushSelf
11763 0x38, PATCH_UINT16(0x0280), // pushi 640d
11764 0x81, 0x5b, // lag global[91] (gloryMessager)
11765 0x4a, PATCH_UINT16(0x0010), // send 16d
11766 0x33, PATCH_GETORIGINALBYTEADJUST(60, +7), // jmp [end the case]
11767 PATCH_END
11768 };
11769
11770 // The castle's great hall (630) has a doorMat region that intermittently sends
11771 // hero back to the room they just left (barrel room) the instant they arrive.
11772 //
11773 // Entry from room 623 starts hero at (0, 157), the edge of the doorMat. We
11774 // shrink the region by 2 pixels. Then sEnterTheRoom moves hero safely across.
11775 // The region is a rectangle. Point 0 is top-left. Point 3 is bottom-left.
11776 //
11777 // Does not apply to English floppy 1.0. It lacked a western doorMat entirely.
11778 //
11779 // Applies to at least: English CD, English floppy, German floppy
11780 // Responsible method: vClosentDoor::init() in script 630
11781 // Fixes bug: #10731
11782 static const uint16 qfg4GreatHallEntrySignature[] = {
11783 SIG_MAGICDWORD,
11784 0x76, // push0 (point 0)
11785 0x38, SIG_UINT16(0x0088), // pushi 136d
11786 SIG_ADDTOOFFSET(+10), // ...
11787 0x76, // push0 0d (point 3)
11788 0x38, SIG_UINT16(0x00b4), // pushi 180d
11789 SIG_END
11790 };
11791
11792 static const uint16 qfg4GreatHallEntryPatch[] = {
11793 0x7a, // push2
11794 PATCH_ADDTOOFFSET(+13), // ...
11795 0x7a, // push2
11796 PATCH_END
11797 };
11798
11799 // In QFG4, the kernel func SetNowSeen() returns void - meaning it doesn't
11800 // modify the accumulator to store a result. Yet math was performed on it!
11801 //
11802 // The function updates boundary box properties of a given View object, prior
11803 // to collision tests. IF (collision detection is warranted, and IF those tests
11804 // are true), THEN respond to the collision.
11805 //
11806 // SetNowSeen() was inserted in the middle of the IF block's list of conditions.
11807 // That way it can be short-circuited, and it runs before the tests.
11808 //
11809 // Problem: void functions make no promise about their truth value. After the
11810 // call, acc will be *whatever* happened to already be in there. This is bad.
11811 //
11812 // "(| (SetNowSeen horror) $0001)"
11813 //
11814 // Someone wrapped the func in a bitwise OR against 1. Thus *every* value is
11815 // guaranteed to become non-zero, always true. And the IF block won't break.
11816 //
11817 // In later SCI2 versions, SetNowSeen() would change to return a boolean.
11818 //
11819 // Whether a lucky confusion or ugly hack, the wrapped void IF condition works.
11820 // When an object leaks into the accumulator. SSCI doesn't mind OR'ing it, too.
11821 // ScummVM detects unsafe arithmetic and crashes. ScummVM needs proper numbers.
11822 //
11823 // "Invalid arithmetic operation (bitwise OR - params: 002e:1694 and 0000:0001)"
11824 //
11825 // We leave the OR wrapper. When the call returns, we manually feed the OR a
11826 // literal 1. Same effect. The IF block goes on evaluating conditions.
11827 //
11828 // Wraith, Vorpal Bunny, and Badder scripts are not affected.
11829 //
11830 // Applies to at least: English CD, English floppy, German floppy
11831 // Responsible method:
11832 // (ego1, combat hero) Script 41 - xSlash::doit(), xDuck::doit(), xParryLow::doit()
11833 // (ego1, combat hero) Script 810 - slash::doit()
11834 // (Revenant) Script 830 - revenantForward::doit()
11835 // (Wyvern) Script 835 - doRSlash::doit(), doLSlash::doit(), tailAttack::doit()
11836 // (Chernovy) Script 840 - doLSlash::doit(), doRSlash::doit()
11837 // (Pit Horror) Script 855 - wipeSpell::doit()
11838 // (Necrotaur) Script 870 - attackLeft::doit(), attackRight::doit(),
11839 // headAttack::doit(), hurtMyself::changeState(1)
11840 // Fixes bug: #10138, #10419, #10710, #10814
11841 static const uint16 qfg4ConditionalVoidSignature[] = {
11842 SIG_MAGICDWORD,
11843 0x43, 0x0a, SIG_UINT16(0x0002), // callk SetNowSeen, 2d (update bounds for a stacked View)
11844 0x36, // push (void func didn't set acc!)
11845 0x35, 0x01, // ldi 1d
11846 0x14, // or (whatever that was, make it non-zero)
11847 SIG_END
11848 };
11849
11850 static const uint16 qfg4ConditionalVoidPatch[] = {
11851 PATCH_ADDTOOFFSET(+4), //
11852 0x78, // push1 (feed OR a literal 1)
11853 PATCH_END
11854 };
11855
11856 // In the graveyard rescuing Igor, ropes are briefly obscured by crypt pillars
11857 // in the background Pic. The Pic assigns a priority to the pillars for depth.
11858 // Ropes are initialized without priority. Then there's a setPri() call.
11859 //
11860 // The floppy edition's Actor::doit() readily calls UpdateScreenItem(). Thus it
11861 // promptly responds to the new priority, bringing the ropes to the front.
11862 //
11863 // The CD edition changed Actor to require a bit flag on the "signal" property
11864 // before it would call UpdateScreenItem(). So the CD edition graphics don't
11865 // update until much later, when the ropes begin an animation.
11866 //
11867 // We patch the heap for script 500 (the graveyard) to give rope1 and rope2
11868 // that "signal" bit as soon as they're created. This'll be toggled with
11869 // enablePatch() below to only apply to the CD edition.
11870 //
11871 // Applies to at least: English CD
11872 // Responsible method: Actor::doit() in script 64998
11873 // Fixes bug: #10751
11874 static const uint16 qfg4GraveyardRopeSignature1[] = {
11875 SIG_MAGICDWORD, // (rope1 properties)
11876 SIG_UINT16(0x0064), // x = 100d
11877 SIG_UINT16(0xfff6), // y = -10d
11878 SIG_ADDTOOFFSET(+24), // ...
11879 SIG_UINT16(0x01f6), // view = 502d
11880 SIG_ADDTOOFFSET(+8), // ...
11881 SIG_UINT16(0x6000), // signal = 0x6000
11882 SIG_END
11883 };
11884
11885 static const uint16 qfg4GraveyardRopePatch1[] = {
11886 PATCH_ADDTOOFFSET(+38),
11887 PATCH_UINT16(0x6001), // signal = 0x6001
11888 PATCH_END
11889 };
11890
11891 static const uint16 qfg4GraveyardRopeSignature2[] = {
11892 SIG_MAGICDWORD, // (rope2 properties)
11893 SIG_UINT16(0x007f), // x = 127d
11894 SIG_UINT16(0xfffb), // y = -5d
11895 SIG_ADDTOOFFSET(+24), // ...
11896 SIG_UINT16(0x01f6), // view = 502d
11897 SIG_ADDTOOFFSET(+8), // ...
11898 SIG_UINT16(0x6000), // signal = 0x6000
11899 SIG_END
11900 };
11901
11902 static const uint16 qfg4GraveyardRopePatch2[] = {
11903 PATCH_ADDTOOFFSET(+38),
11904 PATCH_UINT16(0x6001), // signal = 0x6001
11905 PATCH_END
11906 };
11907
11908 // Rooms 622 and 623 play an extra door sound when entering. They both
11909 // delegate to script 645. It schedules sEnter, which indeed has an extra
11910 // sound. The CD edition removed the line. We remove it, too.
11911 //
11912 // Applies to at least: English floppy, German floppy
11913 // Responsible method: sEnter::changeState(4) in script 645
11914 // Fixes bug: #10827
11915 static const uint16 qfg4DoubleDoorSoundSignature[] = {
11916 0x35, 0x04, // ldi 4d (state 4)
11917 SIG_ADDTOOFFSET(+3), // ...
11918 SIG_MAGICDWORD,
11919 0x39, SIG_SELECTOR8(play), // pushi play
11920 0x76, // push0
11921 0x72, SIG_UINT16(0x0376), // lofsa doorSound
11922 0x4a, SIG_UINT16(0x0004), // send 4d
11923 SIG_END
11924 };
11925
11926 static const uint16 qfg4DoubleDoorSoundPatch[] = {
11927 PATCH_ADDTOOFFSET(+5),
11928 0x33, 0x07, // jmp 7d [skip waste bytes]
11929 PATCH_END
11930 };
11931
11932 // In the castle's iron safe room (643), the righthand door may send hero west
11933 // instead of east - if it was oiled before it was opened (not picked).
11934 //
11935 // The room uses local[2] to remember which door it last decided was nearest.
11936 // The proximity check when opening the right door doesn't reliably set
11937 // local[2]. The assignment was buried inside an IF block testing the oiled
11938 // flag to decide whether the door should squeak. So if the door's been oiled,
11939 // local[2] is not set. If hero had entered the safe from from the west,
11940 // rm643::init() would set local[2] to the left door, and sOpenTheDoor would
11941 // remember LEFT as it decided where to send hero to next.
11942 //
11943 // We move the local[2] assignment out of the IF block, to always run.
11944 //
11945 // Applies to at least: English CD, English floppy, German floppy
11946 // Responsible method: sOpenTheDoor::changeState(0) in script 643
11947 // Fixes bug: #10829
11948 static const uint16 qfg4SafeDoorEastSignature[] = {
11949 SIG_MAGICDWORD, // (else block, right door)
11950 0x78, // push1 (1 call arg)
11951 0x38, SIG_UINT16(0x00d7), // pushi 215d (right door oiled flag)
11952 0x45, 0x04, SIG_UINT16(0x0002), // callb [export 4 of script 0], 2d (test flag 215)
11953 0x18, // not
11954 0x31, SIG_ADDTOOFFSET(+1), // bnt ?? [end the else block]
11955 //
11956 0x35, 0x00, // ldi 0
11957 0xa3, 0x02, // sal local[2]
11958 SIG_END
11959 };
11960
11961 static const uint16 qfg4SafeDoorEastPatch[] = {
11962 0x35, 0x00, // ldi 0
11963 0xa3, 0x02, // sal local[2]
11964 //
11965 0x78, // push1 (1 call arg)
11966 0x38, PATCH_UINT16(0x00d7), // pushi 215d (right door oiled flag)
11967 0x45, 0x04, PATCH_UINT16(0x0002), // callb [export 4 of script 0], 2d (test flag 215)
11968 0x18, // not
11969 0x31, PATCH_GETORIGINALBYTEADJUST(10, -4), // bnt ?? [end the else block]
11970 PATCH_END
11971 };
11972
11973 // In the castle's iron safe room (643), plot flags are mixed up. When hero
11974 // oils either door, the other door's flag is set. Adjacent rooms oil their
11975 // respective doors properly from the outside. We switch the flags inside.
11976 //
11977 // Applies to at least: English CD, English floppy, German floppy
11978 // Responsible method: vBackDoor::doVerb(32), vLeftDoor::doVerb(32) in script 643
11979 // Fixes bug: #10829
11980 static const uint16 qfg4SafeDoorOilSignature[] = {
11981 0x35, 0x20, // ldi 32d (vBackDoor::doVerb(oil), right door)
11982 SIG_ADDTOOFFSET(+5), // ...
11983 SIG_MAGICDWORD,
11984 0x38, SIG_UINT16(0x00d6), // pushi 214d (left oiled flag!?)
11985 0x45, 0x02, SIG_UINT16(0x0002), // callb [export 2 of script 0], 2d (set flag 214)
11986
11987 SIG_ADDTOOFFSET(+152), // ...
11988
11989 0x35, 0x20, // ldi 32d (vLeftDoor::doVerb(oil), left door)
11990 SIG_ADDTOOFFSET(+5), // ...
11991 0x38, SIG_UINT16(0x00d7), // pushi 215d (right oiled flag!?)
11992 0x45, 0x02, SIG_UINT16(0x0002), // callb [export 2 of script 0], 2d (set flag 215)
11993 SIG_END
11994 };
11995
11996 static const uint16 qfg4SafeDoorOilPatch[] = {
11997 PATCH_ADDTOOFFSET(+7),
11998 0x38, PATCH_UINT16(0x00d7), // pushi 215d (right door, set right oiled flag)
11999 PATCH_ADDTOOFFSET(+4+152+7),
12000 0x38, PATCH_UINT16(0x00d6), // pushi 214d (left door, set left oiled flag)
12001 PATCH_END
12002 };
12003
12004 // Waking after a dream by the staff in town (room 270) prevents the room from
12005 // creating a doorMat at nightfall, if hero rests repeatedly. The town gate
12006 // closes at night. Without the doorMat, hero isn't prompted to climb over the
12007 // gate. Instead, hero casually walks south and gets stuck in the next room
12008 // behind the closed gate.
12009 //
12010 // Since hero wakes in the morning, sAfterTheDream disposes any existing
12011 // doorMat. It neglects to reset local[2], which toggles rm270::doit()'s
12012 // constant checks for nightfall to replace the doorMat.
12013 //
12014 // We cache an object lookup and use the spare bytes to reset local[2].
12015 //
12016 // Note: There was never any sunrise detection. If hero rests repeatedly until
12017 // morning, the doorMat will linger to needlessly prompt about climbing the
12018 // then-open gate. Harmless. The prompt sets global[423] (1=climb, 2=levitate).
12019 // The gate room only honors that global at night, so hero will simply walk
12020 // through. Heroes unable to climb/levitate would be denied until they re-enter
12021 // the room.
12022 //
12023 // Applies to at least: English CD, English floppy, German floppy
12024 // Responsible method: sAfterTheDream::changeState(2) in script 270
12025 // Fixes bug: #10830
12026 static const uint16 qfg4DreamGateSignature[] = {
12027 SIG_MAGICDWORD,
12028 0x39, SIG_SELECTOR8(heading), // pushi heading
12029 0x76, // push0
12030 0x72, SIG_ADDTOOFFSET(+2), // lofsa fSouth
12031 0x4a, SIG_UINT16(0x0004), // send 4d
12032 0x31, 0x1a, // bnt 26d [skip disposing/nulling] (no heading)
12033 //
12034 0x38, SIG_SELECTOR16(dispose), // pushi dispose
12035 0x76, // push0
12036 0x39, SIG_SELECTOR8(heading), // pushi heading
12037 0x76, // push0
12038 0x72, SIG_ADDTOOFFSET(+2), // lofsa fSouth
12039 0x4a, SIG_UINT16(0x0004), // send 4d (accumulate heading)
12040 0x4a, SIG_UINT16(0x0004), // send 4d (dispose heading)
12041 //
12042 0x39, SIG_SELECTOR8(heading), // pushi heading
12043 0x78, // push1
12044 0x76, // push0
12045 0x72, SIG_ADDTOOFFSET(+2), // lofsa fSouth
12046 0x4a, SIG_UINT16(0x0006), // send 6d (set fSouth's heading to null)
12047 SIG_END
12048 };
12049
12050 static const uint16 qfg4DreamGatePatch[] = {
12051 0x3f, 0x01, // link 1d (cache heading for reuse)
12052 0x39, PATCH_SELECTOR8(heading), // pushi heading
12053 0x76, // push0
12054 0x72, PATCH_GETORIGINALUINT16(4), // lofsa fSouth
12055 0x4a, PATCH_UINT16(0x0004), // send 4d
12056 0xa5, 0x00, // sat temp[0]
12057 0x31, 0x13, // bnt 19d [skip disposing/nulling] (no heading)
12058 //
12059 0x38, PATCH_SELECTOR16(dispose), // pushi dispose
12060 0x76, // push0
12061 0x85, 0x00, // lat temp[0]
12062 0x4a, PATCH_UINT16(0x0004), // send 4d (dispose: heading:)
12063 //
12064 0x39, PATCH_SELECTOR8(heading), // pushi heading
12065 0x78, // push1
12066 0x76, // push0
12067 0x72, PATCH_GETORIGINALUINT16(4), // lofsa fSouth
12068 0x4a, PATCH_UINT16(0x0006), // send 6d (set fSouth's heading to null)
12069 //
12070 0x76, // push0
12071 0xab, 0x02, // ssl local[2] (let doit() watch for nightfall)
12072 PATCH_END
12073 };
12074
12075 // When approaching the town gate at night in room 270, dismissing the menu
12076 // often doesn't work and instead repeats the gate message and menu. Upon
12077 // entering the gate's doormat, sTo290Night moves hero up by 6 pixels, assuming
12078 // that this places him outside the doormat. That assumption is usually wrong
12079 // since it depends on hero's start position, walk/run mode, and game speed.
12080 //
12081 // We fix this by moving hero one pixel above the doormat instead.
12082 //
12083 // Applies to: All versions
12084 // Responsible method: sTo290Night:changeState(1)
12085 // Fixes bug: #10995
12086 static const uint16 qfg4TownGateDoormatSignature[] = {
12087 0x7a, // push2 [ y ]
12088 0x76, // push0
12089 0x81, 0x00, // lag 00
12090 0x4a, SIG_UINT16(0x0004), // send 04 [ hero:y? ]
12091 SIG_MAGICDWORD,
12092 0x36, // push
12093 0x35, 0x06, // ldi 06
12094 0x04, // sub
12095 0x36, // push [ hero:y - 6 ]
12096 SIG_END
12097 };
12098
12099 static const uint16 qfg4TownGateDoormatPatch[] = {
12100 0x38, PATCH_UINT16(0x00b4), // pushi 180d [ 1 pixel above doormat ]
12101 0x33, 0x07, // jmp 07
12102 PATCH_END
12103 };
12104
12105 // Some inventory item properties leak across restarts. Most conspicuously, the
12106 // torch icon appears pre-lit after a restart, if it had been lit before.
12107 //
12108 // script 16 - thePiepan (item #28): loop, cel, value
12109 // script 35 - theBroom (item #39): cel, value
12110 // script 35 - theTorch (item #44): cel
12111 //
12112 // Glory::restart() tries to revert a bunch of globals to their original state.
12113 // Each value was individually loaded into acc and assigned (ldi+sag, ldi+sag,
12114 // ldi+sag, etc).
12115 //
12116 // One range of globals could be distilled to multiples of 45. We optimize
12117 // those with a loop. Another range was arbitrary. We stack up those values all
12118 // at once, then loop over the stack to pop(), assign, and do the next global.
12119 // We use the freed bytes to reset the 3 items' properties with a subroutine.
12120 //
12121 // Applies to at least: English CD, English floppy, German floppy
12122 // Responsible method: Glory::restart() in script 0
12123 // Fixes bug: #10768
12124 static const uint16 qfg4RestartSignature[] = {
12125 SIG_MAGICDWORD,
12126 0x76, // push0 (this range is multiples of 45)
12127 0x35, 0x01, // ldi 1d
12128 0xb1, 0x90, // sagi (global[144 + 1] = 0)
12129 SIG_ADDTOOFFSET(+40), // ...
12130 0x38, SIG_UINT16(0x013b), // pushi 315d
12131 SIG_ADDTOOFFSET(+2), // ldi ?? (array index here was typoed)
12132 0xb1, 0x90, // sagi (global[144 + ?] = 315)
12133
12134 0x35, 0x14, // ldi 20d (this assignment doesn't fit a pattern)
12135 0xa1, 0xc6, // sag global[198]
12136
12137 0x35, 0x02, // ldi 2d (this range has arbitrary values)
12138 0xa0, SIG_UINT16(0x016f), // sag global[367]
12139 SIG_ADDTOOFFSET(+95), // ...
12140 0x35, 0x0a, // ldi 10d
12141 0xa0, SIG_UINT16(0x0183), // sag global[387]
12142 SIG_END
12143 };
12144
12145 static const uint16 qfg4RestartPatch[] = {
12146 // (loop to assign multiples of 45)
12147 0x76, // push0
12148 0xad, 0x00, // sst temp[0]
12149 //
12150 0x8d, 0x00, // lst temp[0] (global[144 + n+1] = n*45)
12151 0x35, 0x08, // ldi 8d
12152 0x22, // lt?
12153 0x31, 0x0c, // bnt 12d [end the loop]
12154 0x8d, 0x00, // lst temp[0]
12155 0x35, 0x2d, // ldi 45d
12156 0x06, // mul
12157 0x36, // push (temp[0] * 45)
12158 0xc5, 0x00, // +at temp[0]
12159 0xb1, 0x90, // sagi (global[144 + temp[0]])
12160 0x33, 0xed, // jmp -19d (loop)
12161 // (that loop freed +30 bytes)
12162
12163 0x35, 0x14, // ldi 20d (leave this assignment as-is)
12164 0xa1, 0xc6, // sag global[198]
12165
12166 // (stack up arbitrary values; then loop to assign them)
12167 0x7a, // push2 (global[367] = 2)
12168 0x3c, // dup (global[368] = 2)
12169 0x39, 0x03, // pushi 3d (global[369] = 3)
12170 0x3c, // dup (global[370] = 3)
12171 0x3c, // dup (global[371] = 3)
12172 0x39, 0x04, // pushi 4d (global[372] = 4)
12173 0x39, 0x05, // pushi 5d (global[373] = 5)
12174 0x3c, // dup (global[374] = 5)
12175 0x39, 0x06, // pushi 6d (global[375] = 6)
12176 0x39, 0x07, // pushi 7d (global[376] = 7)
12177 0x39, 0x08, // pushi 8d (global[377] = 8)
12178 0x3c, // dup (global[378] = 8)
12179 0x39, 0x05, // pushi 5d (global[379] = 5)
12180 0x39, 0x0a, // pushi 10d (global[380] = 10)
12181 0x39, 0x0f, // pushi 15d (global[381] = 15)
12182 0x39, 0x14, // pushi 20d (global[382] = 20)
12183 0x39, 0x06, // pushi 6d (global[383] = 6)
12184 0x39, 0x08, // pushi 8d (global[384] = 8)
12185 0x39, 0x07, // pushi 7d (global[385] = 7)
12186 0x39, 0x0a, // pushi 10d (global[386] = 10)
12187 0x3c, // dup (global[387] = 10)
12188 //
12189 0x39, 0x15, // pushi 21d (pop() and set, backward from 20 to 0)
12190 0xad, 0x00, // sst temp[0]
12191 //
12192 0xed, 0x00, // -st temp[0]
12193 0x35, 0x00, // ldi 0
12194 0x20, // ge?
12195 0x31, 0x07, // bnt 7d [end the loop]
12196 0x85, 0x00, // lat temp[0]
12197 0xb8, PATCH_UINT16(0x016f), // ssgi (global[367 + n] = pop())
12198 0x33, 0xf2, // jmp -14d (loop)
12199 // (that loop freed +52 bytes)
12200
12201 // (reset properties for a few items)
12202 0x33, 0x1f, // jmp 31d [skip subroutine declaration]
12203 0x38, PATCH_SELECTOR16(loop), // pushi loop
12204 0x78, // push1
12205 0x8f, 0x02, // lsp param[2] (loop varies)
12206 0x38, PATCH_SELECTOR16(cel), // pushi cel
12207 0x78, // push1
12208 0x8f, 0x03, // lsp param[3] (cel varies)
12209 0x38, PATCH_SELECTOR16(value), // pushi value (weight)
12210 0x78, // push1
12211 0x7a, // push2 (these items all weigh 2)
12212 //
12213 0x39, PATCH_SELECTOR8(at), // pushi at
12214 0x78, // push1
12215 0x8f, 0x01, // lsp param[1]
12216 0x81, 0x09, // global[9] (gloryInv)
12217 0x4a, PATCH_UINT16(0x0006), // send 6d
12218 //
12219 0x4a, PATCH_UINT16(0x0012), // send 18d
12220 0x48, // ret
12221
12222 0x39, 0x03, // pushi 3d (call has 3 args)
12223 0x39, 0x1c, // pushi 28d (thePiePan)
12224 0x7a, // push2 (loop)
12225 0x39, 0x0a, // pushi 10d (cel)
12226 0x40, PATCH_UINT16(0xffd5), PATCH_UINT16(0x0006), // call [-43], 6d
12227
12228 0x39, 0x03, // pushi 3d (call has 3 args)
12229 0x39, 0x27, // pushi 39d (theBroom)
12230 0x39, 0x0a, // pushi 10d (loop)
12231 0x76, // push0 (cel)
12232 0x40, PATCH_UINT16(0xffc9), PATCH_UINT16(0x0006), // call [-55], 6d
12233
12234 0x39, 0x03, // pushi 3d (call has 3 args)
12235 0x39, 0x2c, // pushi 44d (theTorch)
12236 0x39, 0x08, // pushi 8d (loop)
12237 0x39, 0x09, // pushi 9d (cel)
12238 0x40, PATCH_UINT16(0xffbc), PATCH_UINT16(0x0006), // call [-68], 6d
12239
12240 0x33, 0x0a, // jmp 10d [skip waste bytes]
12241 PATCH_END
12242 };
12243
12244 // At the squid monolith (room 800), using the grapnel on the eastern ledge
12245 // disposes hero's scaler to freeze hero's size. A scaler dynamically shrinks
12246 // hero into the horizon as y-pos increases. It makes sense that hero should
12247 // maintain their size while climbing a vertical rope.
12248 //
12249 // Problem: After climbing the rope, both standing on the ledge and back on the
12250 // ground, hero's scaler will remain null. An exception will occur if a script
12251 // calls Prop::setScaler(hero) while hero's scaler is null. Casting Trigger on
12252 // the monolith, from either location, does it. As will climbing down, then
12253 // casting Levitate. Both spells have auras intended to fit hero's size. They
12254 // expect hero to have a scaler, not null.
12255 //
12256 // Ideally the climb script would've swapped in a dummy scaler, then swapped
12257 // the original scaler back upon return to ground level. Implementing that with
12258 // patches would be messy. There's no room to patch setScaler() itself to
12259 // broadly tolerate nulls. That'd avoid exceptions but wouldn't restore normal
12260 // scaling after a climb.
12261 //
12262 // As a last resort, we simply leave the original scaler on hero, erasing the
12263 // setScale() call that would freeze hero's size. The hero shrinks/grows a
12264 // little while climbing as a side effect, but that's barely noticeable.
12265 //
12266 // Applies to at least: English CD, English floppy, German floppy
12267 // Responsible method: sUseTheGrapnel::changeState(5) in script 800
12268 // Fixes bug: #10837
12269 static const uint16 qfg4RopeScalerSignature[] = {
12270 SIG_MAGICDWORD,
12271 0x38, SIG_SELECTOR16(setScale), // pushi setScale
12272 0x76, // push0 (no args, disposes scaler & freezes size)
12273 SIG_ADDTOOFFSET(+14), // ...
12274 0x81, 0x00, // lag global[0] (hero)
12275 0x4a, SIG_UINT16(0x0026), // send 38d
12276 SIG_END
12277 };
12278
12279 static const uint16 qfg4RopeScalerPatch[] = {
12280 0x35, 0x01, // ldi 0 (erase 2 bytes)
12281 0x35, 0x01, // ldi 0 (erase 2 bytes)
12282 PATCH_ADDTOOFFSET(+14+2), // ...
12283 0x4a, PATCH_UINT16(0x0022), // send 34d
12284 PATCH_END
12285 };
12286
12287 // The fortune teller's third reading has the wrong card at the center. The 3rd
12288 // and 4th reading are about different people, yet both have "Queen of Cups".
12289 //
12290 // The 1st reading establishes "Queen of Cups" as: a woman of wisdom and love,
12291 // kind, generous, and virtuous. This fits the 4th reading: she uses her power
12292 // joyfully, giving gracefully and lovingly to others.
12293 //
12294 // The 1st reading establishes "Queen of Swords" as: a deceiver or deceived,
12295 // having suffered through terrible hardship, she faces her sorrows bravely,
12296 // but with deep loneliness. This fits the 3rd reading better: some cruel event
12297 // shaped her life... ambition, self-deception, and she is falling in love.
12298 //
12299 // We change the 3rd reading's center card to "Queen of Swords".
12300 //
12301 // Applies to at least: English CD, English floppy, German floppy
12302 // Responsible method: sThirdReading:changeState(3) in script 475
12303 // Fixes bug: #10824
12304 static const uint16 qfg4Tarot3QueenSignature[] = {
12305 SIG_MAGICDWORD,
12306 0x34, SIG_UINT16(0x03f1), // ldi 1009d ("Queen of Cups")
12307 0xa3, 0x00, // sal local[0]
12308 SIG_ADDTOOFFSET(+46), // ...
12309 0x39, 0x1f, // pushi 31d (say: 1 6 31 0 self)
12310 SIG_END
12311 };
12312
12313 static const uint16 qfg4Tarot3QueenPatch[] = {
12314 0x34, PATCH_UINT16(0x03ed), // ldi 1005d ("Queen of Swords")
12315 PATCH_END
12316 };
12317
12318 // The fortune teller's third reading displays a right-turned "The Devil" card,
12319 // but Magda says it is "Death".
12320 //
12321 // We change the card to a right-turned "Death".
12322 //
12323 // Applies to at least: English CD, English floppy, German floppy
12324 // Responsible method: sThirdReading:changeState(15) in script 475
12325 // Fixes bug: #10823
12326 static const uint16 qfg4Tarot3DeathSignature[] = {
12327 SIG_MAGICDWORD,
12328 0x34, SIG_UINT16(0x03fa), // ldi 1018d ("The Devil")
12329 0xa3, 0x00, // sal local[0]
12330 SIG_ADDTOOFFSET(+46), // ...
12331 0x39, 0x23, // pushi 35d (say: 1 6 35 0 self)
12332 SIG_END
12333 };
12334
12335 static const uint16 qfg4Tarot3DeathPatch[] = {
12336 0x34, PATCH_UINT16(0x03fd), // ldi 1021d ("Death")
12337 PATCH_END
12338 };
12339
12340 // The fortune teller's third reading places the "Two of Cups" across another
12341 // card, off-center. That View (1023) is cropped (130x64). All other horizontal
12342 // cards (130x86, 130x87) are padded at the bottom with transparent pixels.
12343 //
12344 // A utility script (sShowCard) is scheduled to create each card with a given
12345 // View (passed as local[0]) and move it onto a given pile (passed as local[2]:
12346 // center, west, south, east, north). It has a switch block deciding x,y coords
12347 // depending on the pile.
12348 //
12349 // We optimize a couple cases by consolidating their common code in a
12350 // subroutine to make room. The rewritten case for the east pile checks if the
12351 // requested card was "Two of Cups". If so, a special Y value is used.
12352 //
12353 // Applies to at least: English CD, English floppy, German floppy
12354 // Responsible method: sShowCard::changeState() in script 475
12355 // Fixes bug: #10822
12356 static const uint16 qfg4Tarot3TwoOfCupsSignature[] = {
12357 SIG_MAGICDWORD,
12358 0x3c, // dup
12359 0x35, 0x04, // ldi 4d (case 4)
12360 0x1a, // eq?
12361 SIG_ADDTOOFFSET(+7), // ...
12362 0x38, SIG_SELECTOR16(setStep), // pushi setStep
12363 SIG_ADDTOOFFSET(+11), // ...
12364 0x51, SIG_ADDTOOFFSET(+1), // class Scaler
12365 SIG_ADDTOOFFSET(+16), // ...
12366 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo
12367 SIG_ADDTOOFFSET(+72), // ...
12368 0x3a, // toss (end of this local[2] switch)
12369 0x32, SIG_ADDTOOFFSET(+2), // jmp [end the state switch]
12370 SIG_END
12371 };
12372
12373 static const uint16 qfg4Tarot3TwoOfCupsPatch[] = {
12374 0x33, 0x31, // jmp 49d [skip subroutine declaration]
12375 0x38, PATCH_SELECTOR16(moveSpeed), // pushi moveSpeed
12376 0x78, // push1
12377 0x76, // push0
12378 0x38, PATCH_SELECTOR16(setStep), // pushi setStep
12379 0x7a, // push2
12380 0x39, 0x1e, // pushi 30d
12381 0x39, 0x0a, // pushi 10d
12382 0x38, PATCH_SELECTOR16(setScaler), // pushi setScaler
12383 0x39, 0x05, // pushi 5d
12384 0x51, PATCH_GETORIGINALBYTE(26), // class Scaler
12385 0x36, // push
12386 0x39, 0x64, // pushi 100d
12387 0x39, 0x23, // pushi 35d
12388 0x38, PATCH_UINT16(0x0096), // pushi 150d
12389 0x8f, 0x01, // lsp param[1] (setScalar, arg 5 varies)
12390 0x38, PATCH_SELECTOR16(setMotion), // pushi setMotion
12391 0x39, 0x04, // pushi 4d
12392 0x51, PATCH_GETORIGINALBYTE(44), // class MoveTo
12393 0x36, // push
12394 0x8f, 0x02, // lsp param[2] (setMotion, x arg varies)
12395 0x8f, 0x03, // lsp param[3] (setMotion, y arg varies)
12396 0x7c, // pushSelf
12397 0x83, 0x01, // lal local[1]
12398 0x4a, PATCH_UINT16(0x0028), // send 40d
12399 0x48, // ret
12400
12401 0x3c, // dup
12402 0x35, 0x04, // ldi 4d (case 4)
12403 0x1a, // eq?
12404 0x31, 0x1b, // bnt 27d [next case]
12405 0x39, 0x03, // pushi 3d (call has 3 args)
12406 0x39, 0x6e, // pushi 110d (setScalar, arg 5)
12407 0x38, PATCH_UINT16(0x00d2), // pushi 210d (setMotion, x arg)
12408 //
12409 0x8b, 0x00, // lsl local[0] (test the card's View number)
12410 0x34, PATCH_UINT16(0x03ff), // ldi 1023d ("Two of Cups" is special)
12411 0x1a, // eq?
12412 0x31, 0x04, // bnt 4d [regular y arg]
12413 0x39, 0x66, // pushi 102d (setMotion, special y arg)
12414 0x33, 0x02, // jmp 2d [to the call]
12415 0x39, 0x6e, // pushi 110d (setMotion, regular y arg)
12416 //
12417 0x41, 0xb0, PATCH_UINT16(0x0006), // call [-80], 6d
12418 0x33, 0x13, // jmp 19d [end the local[2] switch]
12419
12420 0x3c, // dup
12421 0x35, 0x05, // ldi 5d (case 5)
12422 0x1a, // eq?
12423 0x31, 0x0d, // bnt 13d [end the local[2] switch]
12424 0x39, 0x03, // pushi 3d (call has 3 args)
12425 0x39, 0x32, // pushi 50d (setScalar, arg 5)
12426 0x38, PATCH_UINT16(0x0090), // pushi 144d (setMotion, x arg)
12427 0x39, 0x32, // pushi 50d (setMotion, y arg)
12428 0x41, 0x9b, PATCH_UINT16(0x0006), // call [-101], 6d
12429
12430 0x33, 0x0c, // jmp 12d [skip to the original toss that ends this switch]
12431 PATCH_END
12432 };
12433
12434 // The fortune teller's third reading places horizontal cards on top of
12435 // vertical ones. Some then fall *through* the vertical card to the bottom.
12436 //
12437 // A utility script (sShowCard) creates each card and moves it onto a pile
12438 // (center, west, south, east, north). Then the card is assigned a priority
12439 // with setPri(). Generally these piles are two cards deep. Center has one.
12440 //
12441 // Every pile ought to start with priority 0 and increment thereafter. Somebody
12442 // mixed up the setPri() sequence. We change 0;1,0;1,0 to 0;0,1;0,1.
12443 //
12444 // Applies to at least: English CD, English floppy, German floppy
12445 // Responsible method: sThirdReading:changeState() in script 475
12446 // Fixes bug: #10845
12447 static const uint16 qfg4Tarot3PrioritySignature[] = {
12448 0x78, // push1 (setPri: 1, "Eight of Swords", West, V)
12449 SIG_ADDTOOFFSET(+14), // ...
12450 0x39, 0x20, // pushi 32d (say cond:32)
12451 SIG_ADDTOOFFSET(+68), // ...
12452 0x76, // push0 (setPri: 0, "Strength", West, H)
12453 SIG_ADDTOOFFSET(+84), // ...
12454 0x78, // push1 (setPri: 1, "The Magician", South, V)
12455 SIG_ADDTOOFFSET(+84), // ...
12456 0x76, // push0 (setPri: 0, "Death", South, H)
12457 SIG_ADDTOOFFSET(+12), // ...
12458 SIG_MAGICDWORD,
12459 0x39, 0x06, // pushi 6d (say verb:6)
12460 0x39, 0x23, // pushi 36d (say cond:35)
12461 SIG_END
12462 };
12463
12464 static const uint16 qfg4Tarot3PriorityPatch[] = {
12465 0x76, // push0 (setPri: 0, "Eight of Swords", West, V)
12466 PATCH_ADDTOOFFSET(+84), // ...
12467 0x78, // push1 (setPri: 1, "Strength", West, H)
12468 PATCH_ADDTOOFFSET(+84), // ...
12469 0x76, // push0 (setPri: 0, "The Magician", South, V)
12470 PATCH_ADDTOOFFSET(+84), // ...
12471 0x78, // push1 (setPri: 1, "Death", South, H)
12472 PATCH_END
12473 };
12474
12475 // The fortune teller's fifth reading is unusual. It places all cards at the
12476 // center pile, periodically fading out to clear the table. When Magda
12477 // talks about the Sense Ritual, the "Six of Swords" (view 1048) sinks below
12478 // the previous card.
12479 //
12480 // A shared utility script that creates and places cards (sSetTheSignificator)
12481 // uses priority 12 as it deals, after which each card is given a lower value
12482 // with setPri(). "The Falling Tower" (view 1031) did *not* get a new priority.
12483 // Thus "Six of Swords", when given priority 1, sinks below 12.
12484 //
12485 // "Six of Swords" is the last card before a fade. We simply leave its priority
12486 // at 12 as well. Being the most recent card, it will be on top. No worry
12487 // about covering a subsequent card because the table will be cleared.
12488 //
12489 // Applies to at least: English CD, English floppy, German floppy
12490 // Responsible method: sFifthReading:changeState(32) in script 475
12491 // Fixes bug: #10846
12492 static const uint16 qfg4Tarot5PrioritySignature[] = {
12493 0x39, SIG_ADDTOOFFSET(+1), // pushi setPri
12494 0x78, // push1
12495 0x78, // push1 (setPri: 1, "Six of Swords")
12496 SIG_MAGICDWORD,
12497 0x83, 0x01, // lal local[1] (card obj)
12498 0x4a, SIG_UINT16(0x0006), // send 6d
12499 SIG_ADDTOOFFSET(+9), // ...
12500 0x39, 0x44, // pushi 68d (say cond:68)
12501 SIG_END
12502 };
12503
12504 static const uint16 qfg4Tarot5PriorityPatch[] = {
12505 0x33, 0x07, // jmp 7d [skip the setPri() send]
12506 PATCH_END
12507 };
12508
12509 // When crossing the cave's tightrope in room 710, a tentacle emerges, and then
12510 // its animation freezes - in ScummVM, not the original interpreter. This
12511 // happens because of an extraneous argument passed to setCycle().
12512 //
12513 // (tentacle setCycle: RandCycle tentacle)
12514 //
12515 // RandCycle can accept an optional arg, but it expects a number, not an
12516 // object. ScummVM doesn't catch the faulty arithmetic and behaves abnormally.
12517 // We remove the bad arg.
12518 //
12519 // Applies to at least: English CD, English floppy, German floppy
12520 // Responsible method: sTentacleDeath::changeState(3) in script 710
12521 // Fixes bug: #10615
12522 static const uint16 qfg4TentacleWriggleSignature[] = {
12523 SIG_MAGICDWORD,
12524 0x38, SIG_SELECTOR16(setCycle), // pushi setCycle
12525 0x7a, // push2
12526 0x51, SIG_ADDTOOFFSET(+1), // class RandCycle
12527 0x36, // push
12528 0x72, SIG_ADDTOOFFSET(+2), // loffsa tentacle
12529 0x36, // push
12530 0x72, SIG_ADDTOOFFSET(+2), // loffsa tentacle
12531 0x4a, SIG_UINT16(0x0008), // send 8d
12532 SIG_END
12533 };
12534
12535 static const uint16 qfg4TentacleWrigglePatch[] = {
12536 PATCH_ADDTOOFFSET(+3),
12537 0x78, // push1 (1 setCycle arg)
12538 PATCH_ADDTOOFFSET(+3), // ...
12539 0x35, 0x00, // ldi 0 (erase 2 bytes)
12540 0x35, 0x00, // ldi 0 (erase 2 bytes)
12541 PATCH_ADDTOOFFSET(+3), // ...
12542 0x4a, PATCH_UINT16(0x0006), // send 6d
12543 PATCH_END
12544 };
12545
12546 // When crossing the cave's tightrope in room 710, a tentacle emerges. The
12547 // tentacle is supposed to reach a state where it waits indefinitely for an
12548 // external cue() to retract. If the speed slider is too high, a fighter
12549 // reaches the other side and sends a cue() before the tentacle is ready to
12550 // receive it. The tentacle never retracts.
12551 //
12552 // The fighter script (crossByHand) drains stamina in state 2 as hero moves
12553 // across. A slower speed would cost extra stamina. We add a delay after that
12554 // part is over, in state 3, just as hero is about to dismount. When state 4
12555 // cues, the tentacle script (sTentacleDeath) will be ready (state 3).
12556 //
12557 // To create that delay we set the "cycles" property for a countdown and remove
12558 // all other advancement mechanisms. State 3 had a cue from say() and a
12559 // self-cue(). The former's "self" arg becomes null. The latter is erased.
12560 //
12561 // Crossing from the left (crossByHandLeft) doesn't require fixing.
12562 //
12563 // This patch doesn't apply to the NRS version which ships with the GOG release
12564 // as it throttles the frequency of crossByHand:doit which fixes the bug.
12565 //
12566 // Applies to at least: English CD, English floppy, German floppy
12567 // Responsible method: crossByHand::changeState(3) in script 710
12568 // Fixes bug: #10615
12569 static const uint16 qfg4PitRopeFighterSignature[] = {
12570 0x65, SIG_ADDTOOFFSET(+1), // aTop state
12571 SIG_ADDTOOFFSET(+269), // ...
12572 0x31, 0x1e, // bnt 30d (set a flag and say() on 1st crossing, else cue)
12573 SIG_ADDTOOFFSET(+8), // ...
12574 0x38, SIG_SELECTOR16(say), // pushi say ("You just barely made it")
12575 0x38, SIG_UINT16(0x0005), // pushi 5d
12576 0x39, 0x0a, // pushi 10d
12577 0x39, 0x06, // pushi 6d
12578 0x39, 0x20, // pushi 32d
12579 0x76, // push0
12580 0x7c, // pushSelf
12581 SIG_ADDTOOFFSET(+5), // ...
12582 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch]
12583 SIG_MAGICDWORD,
12584 0x38, SIG_SELECTOR16(cue), // pushi cue
12585 0x76, // push0
12586 0x54, SIG_UINT16(0x0004), // self 4d
12587 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch]
12588 SIG_END
12589 };
12590
12591 static const uint16 qfg4PitRopeFighterPatch[] = {
12592 PATCH_ADDTOOFFSET(+271), // ... (2 + 269)
12593 0x31, 0x1b, // bnt 26d [skip the say w/o ending the switch]
12594 PATCH_ADDTOOFFSET(+21), // ... (8 + 13)
12595 0x76, // push0 (null caller, so say won't cue crossByHand)
12596 PATCH_ADDTOOFFSET(+5), // ...
12597 // (no jmp, self-cue becomes cycles)
12598 0x35, 0x20, // ldi 32d
12599 0x65, PATCH_GETORIGINALBYTEADJUST(1, +6), // aTop cycles (property offset = @state + 6d)
12600 0x33, 0x04, // jmp 4d [skip waste bytes, end the switch]
12601 PATCH_END
12602 };
12603
12604 // As above, mages at high speed can get across the pit in room 710 before
12605 // tentacle is ready to receive a cue().
12606 //
12607 // As luck would have it, hero's speed is cached and restored. Twice actually.
12608 //
12609 // Overview of the mage script (sLevitateOverPit)
12610 // State 1-3: Cache hero's slider-based speed.
12611 // Set a temporary speed as hero unfurls the cloth.
12612 // Restore the original value.
12613 // Call handsOn(). Wait for an external cue().
12614 // State 4: Call handsOff().
12615 // If cued by the Levitate spell (script 21), go to 5-7.
12616 // A plain cue() from anywhere else, leads to state 8.
12617 // State 5-7: Move across the pit. Skip to state 9.
12618 // State 8: An abort message.
12619 // State 9-10: Cache hero's speed again.
12620 // Set a temporary speed as hero folds up the cloth.
12621 // Restore the original value, and normalize hero. Call handsOn().
12622 //
12623 // Patch 1: We overwrite some derelict code in state 5, caching the
12624 // slider-based speed again, in case the player adjusted it before casting
12625 // Levitate, then setting a fixed speed of our own for the crossing.
12626 //
12627 // Patch 2: Patch 1 already cached and clobbered the speed. We remove the
12628 // original attempt to cache again in state 9.
12629 //
12630 // The result is caching/restoration at the beginning, aborting or caching and
12631 // crossing with our fixed value, and a restoration at the end (whichever value
12632 // was last cached). The added travel time has no side effect for mages.
12633 //
12634 // Mages have no other script to levitate across from left to right. At some
12635 // point in development, the meaning of "register" changed. The derelict
12636 // state 5 code thought 0/1 meant move right/left. Whereas state 4 decides 0/1
12637 // means abort/cross, only ever moving left. The rightward MoveTo never runs.
12638 //
12639 // We also include a version of this for the instruction sizes in the NRS patch,
12640 // which is important as that ships with the GOG version.
12641 //
12642 // Applies to at least: English CD, English floppy, German floppy
12643 // Responsible method: sLevitateOverPit::changeState(5) in script 710
12644 // Fixes bug: #10615
12645 static const uint16 qfg4PitRopeMageSignature1[] = {
12646 0x30, SIG_UINT16(0x0017), // bnt 23d [if register == 0 (never), move right]
12647 SIG_ADDTOOFFSET(+20), // ... (move left)
12648 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch]
12649
12650 0x38, SIG_SELECTOR16(setMotion), // pushi setMotion (move right)
12651 0x38, SIG_UINT16(0x0004), // pushi 4d
12652 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo
12653 0x36, // push
12654 SIG_MAGICDWORD,
12655 0x38, SIG_UINT16(0x00da), // pushi 218d
12656 0x39, 0x30, // pushi 48d
12657 0x7c, // pushSelf
12658 0x81, 0x00, // lag global[0] (hero)
12659 0x4a, SIG_UINT16(0x000c), // send 12d
12660 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch]
12661 SIG_END
12662 };
12663
12664 static const uint16 qfg4PitRopeMagePatch1[] = {
12665 0x34, PATCH_UINT16(0x0000), // ldi 0 (erase the branch)
12666 PATCH_ADDTOOFFSET(+20), // ...
12667
12668 0x38, PATCH_SELECTOR16(cycleSpeed), // pushi cycleSpeed
12669 0x76, // push0
12670 0x81, 0x00, // lag global[0] (hero)
12671 0x4a, PATCH_UINT16(0x0004), // send 4d
12672 0xa3, 0x02, // sal local[2] (cache again)
12673 //
12674 0x38, PATCH_SELECTOR16(setSpeed), // pushi setSpeed
12675 0x78, // push1
12676 0x39, 0x08, // pushi 8d (set our fixed speed)
12677 0x81, 0x00, // lag global[0] (hero)
12678 0x4a, PATCH_UINT16(0x0006), // send 6d
12679 0x5c, // selfID (erase 1 byte to keep disasm aligned)
12680 PATCH_END
12681 };
12682
12683 static const uint16 qfg4PitRopeMageNrsSignature1[] = {
12684 0x30, SIG_UINT16(0x0016), // bnt 22d [if register == 0 (never), move right]
12685 SIG_ADDTOOFFSET(+19), // ... (move left)
12686 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch]
12687
12688 0x38, SIG_SELECTOR16(setMotion), // pushi setMotion (move right)
12689 0x39, 0x04, // pushi 4d
12690 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo
12691 0x36, // push
12692 SIG_MAGICDWORD,
12693 0x38, SIG_UINT16(0x00da), // pushi 218d
12694 0x39, 0x30, // pushi 48d
12695 0x7c, // pushSelf
12696 0x81, 0x00, // lag global[0] (hero)
12697 0x4a, SIG_UINT16(0x000c), // send 12d
12698 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch]
12699 SIG_END
12700 };
12701
12702 static const uint16 qfg4PitRopeMageNrsPatch1[] = {
12703 0x34, PATCH_UINT16(0x0000), // ldi 0 (erase the branch)
12704 PATCH_ADDTOOFFSET(+19), // ...
12705
12706 0x38, PATCH_SELECTOR16(cycleSpeed), // pushi cycleSpeed
12707 0x76, // push0
12708 0x81, 0x00, // lag global[0] (hero)
12709 0x4a, PATCH_UINT16(0x0004), // send 4d
12710 0xa3, 0x02, // sal local[2] (cache again)
12711 //
12712 0x38, PATCH_SELECTOR16(setSpeed), // pushi setSpeed
12713 0x78, // push1
12714 0x39, 0x08, // pushi 8d (set our fixed speed)
12715 0x81, 0x00, // lag global[0] (hero)
12716 0x4a, PATCH_UINT16(0x0006), // send 6d
12717 PATCH_END
12718 };
12719
12720 // Responsible method: sLevitateOverPit::changeState(9) in script 710
12721 static const uint16 qfg4PitRopeMageSignature2[] = {
12722 SIG_MAGICDWORD,
12723 0x35, 0x09, // ldi 9d (case 9 label)
12724 0x38, SIG_SELECTOR16(cycleSpeed), // pushi cycleSpeed
12725 SIG_ADDTOOFFSET(+6), // ...
12726 0xa3, 0x02, // sal local[2] (original re-cache)
12727 SIG_ADDTOOFFSET(+48), // ...
12728 0x38, SIG_SELECTOR16(cycleSpeed), // pushi cycleSpeed
12729 0x78, // push1
12730 0x8b, 0x02, // lsl local[2] (restore cached speed)
12731 SIG_END
12732 };
12733
12734 static const uint16 qfg4PitRopeMagePatch2[] = {
12735 PATCH_ADDTOOFFSET(+11), // (don't cache our fixed speed)
12736 0x35, 0x00, // ldi 0 (erase 2 bytes)
12737 PATCH_ADDTOOFFSET(+48), // ...
12738 0x38, PATCH_SELECTOR16(setSpeed), // pushi setSpeed (keep cycleSpeed & moveSpeed sync'd)
12739 PATCH_END
12740 };
12741
12742 // WORKAROUND: Script needed, because of differences in our pathfinding
12743 // algorithm.
12744 // When entering forest room 557 from the east (563), hero is supposed to move
12745 // only a short distance into the room. ScummVM's pathfinding sends hero off
12746 // course, to the middle of the room and back.
12747 //
12748 // There's an unwalkable stream in the SE corner, and hero's coords were within
12749 // its polygon. We lower the top two points to keep hero on the outside.
12750 //
12751 // Applies to at least: English CD, English floppy, German floppy
12752 // Responsible method: rm557::init() in script 557
12753 // Fixes bug: #10857
12754 static const uint16 qfg4Forest557PathfindingSignature[] = {
12755 SIG_MAGICDWORD,
12756 0x38, SIG_UINT16(0x0119), // pushi 281d (point 3)
12757 0x38, SIG_UINT16(0x0087), // pushi 135d
12758 0x38, SIG_UINT16(0x013f), // pushi 319d (point 4)
12759 0x38, SIG_UINT16(0x0087), // pushi 135d
12760 SIG_END
12761 };
12762
12763 static const uint16 qfg4Forest557PathfindingPatch[] = {
12764 PATCH_ADDTOOFFSET(+3),
12765 0x38, PATCH_UINT16(0x0089), // pushi 137d
12766 PATCH_ADDTOOFFSET(+3),
12767 0x38, PATCH_UINT16(0x0089), // pushi 137d
12768 PATCH_END
12769 };
12770
12771 // The Trigger spell stalls and never reaches handsOn when preceded by a
12772 // successful Summon Staff. An IF block calls hero::setCycle(Beg, self), which
12773 // cues self on completion. Its condition tests hero's "view" property and
12774 // executes if the staff is absent. There are no means to advance when the
12775 // staff is present.
12776 //
12777 // Open (script 13) is largely identical w/o this bug. We match its behavior.
12778 //
12779 // Due to differences between editions, this is addressed with two patches. The
12780 // first inserts a "seconds" property assignment before the IF, where it'll
12781 // always cue. We make room by condensing the IF conditions. There are two
12782 // "view" comparisons. Instead of sending for it twice, we recycle the view
12783 // with pprev. The second patch removes setCycle's cue by nulling its last arg.
12784 //
12785 // Applies to at least: English CD, English floppy, German floppy
12786 // Responsible method: castTriggerScript::changeState(2) in script 11
12787 // Fixes bug: #10860
12788 static const uint16 qfg4TriggerStaffSignature1[] = {
12789 0x65, SIG_ADDTOOFFSET(+1), // aTop register
12790
12791 SIG_ADDTOOFFSET(+9), // ... (send for hero's view, push for comparison)
12792 0x35, 0x11, // ldi 17d
12793 0x1e, // gt?
12794 0x31, SIG_ADDTOOFFSET(+1), // bnt ?? [to the not]
12795 SIG_ADDTOOFFSET(+9), // ... (send for hero's view and push again)
12796 SIG_MAGICDWORD,
12797 0x35, 0x15, // ldi 21d
12798 0x22, // lt?
12799 0x31, SIG_ADDTOOFFSET(+1), // bnt ?? [to the not]
12800 //0x33, 0x00, // (Floppy has a jmp 0 here)
12801 //0x18, // not ( !(view > 17 && view < 21) )
12802 SIG_END
12803 };
12804
12805 static const uint16 qfg4TriggerStaffPatch1[] = {
12806 PATCH_ADDTOOFFSET(+2), // (free bytes later, use them up here)
12807 0x35, 0x03, // ldi 3d
12808 0x65, PATCH_GETORIGINALBYTEADJUST(1, -8), // aTop seconds (property offset = @register - 8d)
12809 0x35, 0x00, // ldi 0 (waste 2 bytes)
12810 0x34, PATCH_UINT16(0x0000), // ldi 0 (waste 3 bytes)
12811
12812 0x39, PATCH_SELECTOR8(view), // pushi view
12813 0x76, // push0
12814 0x81, 0x00, // lag global[0] (hero)
12815 0x4a, PATCH_UINT16(0x0004), // send 4d
12816 //
12817 0x39, 0x11, // pushi 17d (push the literal, leave view in acc)
12818 0x22, // lt? (view > 17 becomes 17 < view, set prev = acc)
12819 0x31, PATCH_GETORIGINALBYTEADJUST(15, -8), // bnt ?? [to the not]
12820 //
12821 0x60, // pprev (push the view from prev, ldi 21 comparison is next)
12822 PATCH_END
12823 };
12824
12825 static const uint16 qfg4TriggerStaffSignature2[] = {
12826 SIG_MAGICDWORD,
12827 0x31, 0x0d, // bnt 13d [conditions failed, skip the send]
12828 0x38, SIG_SELECTOR16(setCycle), // pushi setCycle
12829 0x7a, // push2
12830 0x51, SIG_ADDTOOFFSET(+1), // class Beg
12831 0x36, // push
12832 0x7c, // pushSelf (caller arg is cued afterward)
12833 SIG_END
12834 };
12835
12836 static const uint16 qfg4TriggerStaffPatch2[] = {
12837 PATCH_ADDTOOFFSET(+9),
12838 0x76, // push0 (null caller arg, no cue)
12839 PATCH_END
12840 };
12841
12842 // The Open and Trigger spells init a green Prop for their effect. They don't
12843 // dispose it the first time, and the effect is absent on further castings.
12844 //
12845 // The author specifically nerfed dispose() on its first call by testing if a
12846 // variable has been set before allowing super::dispose(). We erase the
12847 // branch to ensure that the effect is always disposed.
12848 //
12849 // Applies to at least: English CD, English floppy, German floppy
12850 // Responsible method: triggerEffect::dispose() in script 11
12851 // openEffect::dispose() in script 13
12852 // Fixes bug: #10860
12853 static const uint16 qfg4EffectDisposalSignature[] = {
12854 0x83, SIG_ADDTOOFFSET(+1), // lal local[?] (0 on first call, 1 thereafter)
12855 SIG_MAGICDWORD,
12856 0x31, 0x0a, // bnt 10d [skip super::dispose()]
12857 0x38, SIG_SELECTOR16(dispose), // pushi dispose
12858 0x76, // push0
12859 0x57, SIG_ADDTOOFFSET(+1), SIG_UINT16(0x0004), // super Prop, 4d
12860 0x33, 0x04, // jmp 4d [ret]
12861
12862 0x35, 0x01, // ldi 1d (enable normal disposal)
12863 0xa3, SIG_ADDTOOFFSET(+1), // sal local[?]
12864 SIG_END
12865 };
12866
12867 static const uint16 qfg4EffectDisposalPatch[] = {
12868 PATCH_ADDTOOFFSET(+2),
12869 0x35, 0x00, // ldi 0 (erase the branch to always dispose)
12870 PATCH_END
12871 };
12872
12873 // After hero is geas'd in the dungeon (room 670) and teleported to the gate
12874 // (600), hero can walk through the closed gate and exit north to the castle
12875 // entrance. Two IF blocks with inconsistent conditions decide whether the
12876 // gate is open and whether to use a polygon that extends beyond the gate.
12877 // When re-entering from the forest (552), the gate is only open and passable
12878 // if hero is qualified.
12879 //
12880 // The room has distinct situations for merely teleporting from the dungeon
12881 // (local[0] = 11) and for entering from the forest while geas'd and carrying
12882 // all the ritual scrolls (local[0] = 10). The latter sets a vital plot flag.
12883 // Adding those checks and the flag to the former, plus opening the gate,
12884 // would be non-trivial.
12885 //
12886 // We edit the polygon's IF condition to remove the dungeon check, making the
12887 // closed gate impassable so hero will have to return from the forest.
12888 //
12889 // Applies to at least: English CD, English floppy, German floppy
12890 // Responsible method: rm600::init() in script 600
12891 // Fixes bug: #10871
12892 static const uint16 qfg4DungeonGateSignature[] = {
12893 0x39, 0x05, // pushi 5d (5 call args)
12894 0x89, 0x0c, // lsg global[12] (needle value)
12895 SIG_MAGICDWORD,
12896 0x38, SIG_UINT16(0x029e), // pushi 670 (Dungeon)
12897 0x38, SIG_UINT16(0x032a), // pushi 810 (Combat)
12898 0x38, SIG_UINT16(0x0262), // pushi 610 (Castle entrance)
12899 0x38, SIG_UINT16(0x0276), // pushi 630 (Great hall)
12900 0x46, SIG_UINT16(0xfde7), SIG_UINT16(0x0005), SIG_UINT16(0x000a), // calle [export 5 of script 64999], 10d (is needle in haystack?)
12901 SIG_END
12902 };
12903
12904 static const uint16 qfg4DungeonGatePatch[] = {
12905 0x39, 0x04, // pushi 4d (4 call args)
12906 PATCH_ADDTOOFFSET(+2), // ...
12907 0x34, PATCH_UINT16(0x0000), // ldi 0 (erase the Dungeon arg)
12908 PATCH_ADDTOOFFSET(+9), // ...
12909 0x46, PATCH_UINT16(0xfde7), PATCH_UINT16(0x0005), PATCH_UINT16(0x0008), // calle [export 5 of script 64999], 8d (is needle in haystack?)
12910 PATCH_END
12911 };
12912
12913 // In the room (644) attached to the lower door of the bat-infested stairway,
12914 // a rogue will get stuck when attempting to open either door. Unlike in other
12915 // castle rooms, the door Tellers here aren't arranging to be cued after the
12916 // "It won't budge" message. Without the cue, a Teller won't clean() and return
12917 // control to the player.
12918 //
12919 // We follow the style of other rooms and replace gloryMessager::say() with
12920 // super::sayMessage(), which implicitly cues.
12921 //
12922 // Applies to at least: English CD, English floppy, German floppy
12923 // Responsible method: leftDoorTeller::sayMessage(), rightDoorTeller::sayMessage() in script 644
12924 // Fixes bug: #10874
12925 static const uint16 qfg4StuckDoorSignature[] = {
12926 0x38, SIG_SELECTOR16(say), // pushi say
12927 0x38, SIG_UINT16(0x0006), // pushi 6d
12928 0x39, 0x03, // pushi 3d
12929 SIG_MAGICDWORD,
12930 0x39, 0x06, // pushi 6d
12931 0x39, 0x09, // pushi 9d
12932 0x78, // push1
12933 0x76, // push0
12934 0x38, SIG_UINT16(0x0280), // pushi 640d
12935 0x81, 0x5b, // lag global[91]
12936 0x4a, SIG_UINT16(0x0010), // send 16d
12937 SIG_ADDTOOFFSET(+89), // ...
12938 0x57, SIG_ADDTOOFFSET(+1), SIG_UINT16(0x0004), // super Teller, 4d
12939 SIG_END
12940 };
12941
12942 static const uint16 qfg4StuckDoorPatch[] = {
12943 0x38, PATCH_SELECTOR16(sayMessage), // pushi sayMessage
12944 0x39, 0x03, // pushi 3d
12945 0x3c, // dup
12946 0x39, 0x06, // pushi 6d
12947 0x39, 0x09, // pushi 9d
12948 0x59, 0x01, // &rest 1d
12949 0x57, PATCH_GETORIGINALBYTE(112), PATCH_UINT16(0x000a), // super Teller, 10d
12950 0x32, PATCH_UINT16(0x0003), // jmp 3d [skip waste bytes]
12951 PATCH_END
12952 };
12953
12954 // In the thieves' guild (room 430), the tunnel is not immediately walkable
12955 // when it is revealed (by moving a barrel and solving a puzzle). Hero must
12956 // re-enter the room to update the polygon.
12957 //
12958 // Curing Chief *will* immediately replace the polygon. However, most players
12959 // will lack the item necessary on the first visit. Meeting Chief is how they
12960 // learn about the item. If they go get it, they'll re-enter the room.
12961 //
12962 // The room's init has a cond block to check plot flags and declare one of 3
12963 // polygons. The 3rd condition also inits secritExit, an invisible Feature
12964 // that sends hero out of town when walked upon.
12965 //
12966 // Patch 1: Other patches ensure the passage will be walkable the moment it is
12967 // revealed. Chief is standing inside it. We skip the original code that would
12968 // set up the passage as he gets cured. It is redundant now. If hero can reach
12969 // him, the passage is already revealed. We won't let secritExit init twice.
12970 //
12971 // Patch 2: We free bytes in rm340::init() by condensing Feature inits with a
12972 // loop. Stack up their addresses. Pop & send repeatedly. Then we declare a
12973 // subroutine that disposes any existing obstacles, jumps into the cond block
12974 // to declare the 3rd poly, jumps back, passes it to addObstacles(), and inits
12975 // secritExit.
12976 //
12977 // When the cond block's 3rd condition runs, we immediately call our
12978 // subroutine to do everything and end the cond, leaving the original polygon
12979 // declaration intact below the jump.
12980 //
12981 // Patch 3: The passage starts opening at sBarrelMove state 8. We need more
12982 // room than case 8 can offer, so we arrange for *multiple* cases to run
12983 // during state 8 - by omitting the final jump that would short-circuit.
12984 //
12985 // Cases 1-5 have derelict code, once intended to move the barrel back and
12986 // forth, now only left. This is because barrel::doVerb(4) schedules
12987 // sBarrelMove in the absence of flag 254 and sets register=1 if the barrel is
12988 // in the left position already. Case 0 uses the same criteria in deciding to
12989 // skip to state 6. Thus cases 1-5 never see register==1. The barrel never
12990 // moves back, and bytes predicated on register==1 are available.
12991 //
12992 // We reduce case 2 to only the necessary ops and splice in a new case that
12993 // runs during state 8 as a prologue to the original case 8. Our prologue
12994 // calls the subroutine to add the 3rd polygon. This patch has two variants,
12995 // toggled to match the detected edition with enablePatch() below. Aside from
12996 // the call offset, they are identical.
12997 //
12998 // Applies to at least: English CD, English floppy, German floppy
12999 // Responsible method: sChangeThief::changeState() in script 340
13000 // Fixes bug: #9894
13001 static const uint16 qfg4GuildWalkSignature1[] = {
13002 0x38, SIG_SELECTOR16(dispose), // pushi dispose
13003 SIG_ADDTOOFFSET(+20), // ... (dispose and null global[2]'s "obstacles" property)
13004 0x4a, SIG_UINT16(0x0006), // send 6d
13005 SIG_ADDTOOFFSET(+85), // ...
13006 SIG_ADDTOOFFSET(+238), // ... (secritExit init and addObstacle)
13007 SIG_MAGICDWORD,
13008 0x4a, SIG_UINT16(0x00a6), // send 166d (polygon init)
13009 0x36, // push
13010 SIG_ADDTOOFFSET(+5), // ... (global[2] addObstacle: polygon)
13011 SIG_END
13012 };
13013
13014 static const uint16 qfg4GuildWalkPatch1[] = {
13015 0x32, PATCH_UINT16(0x0017), // jmp 23d (skip obstacles disposal)
13016 PATCH_ADDTOOFFSET(+108),
13017 0x32, PATCH_UINT16(0x00f4), // jmp 244d (skip secritExit and polygon)
13018 PATCH_END
13019 };
13020
13021 // Responsible method: rm340::init() in script 340
13022 static const uint16 qfg4GuildWalkSignature2[] = {
13023 0x38, SIG_SELECTOR16(init), // pushi init
13024 0x76, // push0
13025 0x38, SIG_SELECTOR16(approachVerbs), // pushi approachVerbs
13026 0x78, // push1
13027 0x39, 0x04, // pushi 4d
13028 0x72, SIG_ADDTOOFFSET(+2), // lofsa steps1
13029 0x4a, SIG_UINT16(0x000a), // send 10d
13030
13031 SIG_ADDTOOFFSET(+10), // ... (similar inits follow)
13032 0x72, SIG_ADDTOOFFSET(+2), // lofsa steps2
13033 SIG_ADDTOOFFSET(+13), // ...
13034 0x72, SIG_ADDTOOFFSET(+2), // lofsa barrels1
13035 SIG_ADDTOOFFSET(+13), // ...
13036 0x72, SIG_ADDTOOFFSET(+2), // lofsa barrels2
13037 SIG_ADDTOOFFSET(+13), // ...
13038 0x72, SIG_ADDTOOFFSET(+2), // lofsa crack1
13039 SIG_ADDTOOFFSET(+13), // ...
13040 0x72, SIG_ADDTOOFFSET(+2), // lofsa crack2
13041 SIG_ADDTOOFFSET(+13), // ...
13042 0x72, SIG_ADDTOOFFSET(+2), // lofsa pillar
13043 SIG_ADDTOOFFSET(+3), // ...
13044
13045 SIG_ADDTOOFFSET(+26), // ... (global[78]::add() steps1 and steps2)
13046
13047 SIG_ADDTOOFFSET(+459), // ... (cond block for polygons)
13048 SIG_MAGICDWORD,
13049 0x32, SIG_UINT16(0x00f7), // jmp 247d [end the cond] (2nd condition done)
13050
13051 // (else condition)
13052 0x38, SIG_SELECTOR16(init), // pushi init
13053 0x76, // push0
13054 0x72, SIG_ADDTOOFFSET(+2), // lofsa secritExit
13055 0x4a, SIG_UINT16(0x0004), // send 4d
13056 SIG_ADDTOOFFSET(+4), // ... (addObstacle and its arg count)
13057 SIG_ADDTOOFFSET(+228), // ... (3rd Polygon type, init, and push)
13058 SIG_ADDTOOFFSET(+5), // ... (end of the polygons cond)
13059
13060 0x38, SIG_SELECTOR16(init), // pushi init (super init:)
13061 SIG_END
13062 };
13063
13064 static const uint16 qfg4GuildWalkPatch2[] = {
13065 0x3f, 0x02, // link 02 (set up loop vars, op affects the stack)
13066 0x74, PATCH_GETORIGINALUINT16(11), // lofss steps1
13067 0x74, PATCH_GETORIGINALUINT16(27), // lofss steps2
13068 0x74, PATCH_GETORIGINALUINT16(43), // lofss barrels1
13069 0x74, PATCH_GETORIGINALUINT16(59), // lofss barrels2
13070 0x74, PATCH_GETORIGINALUINT16(75), // lofss crack1
13071 0x74, PATCH_GETORIGINALUINT16(91), // lofss crack2
13072 0x74, PATCH_GETORIGINALUINT16(107), // lofss pillar
13073 //
13074 0x35, 0x08, // ldi 8d (decrement and send 7 times, while != 0)
13075 0xa5, 0x00, // sat temp[0]
13076 //
13077 0xe5, 0x00, // -at temp[0]
13078 0x31, 0x13, // bnt 19d [on 0, end the loop]
13079 0xad, 0x01, // sst temp[1] (pop the next object into a temp var)
13080 0x38, PATCH_SELECTOR16(init), // pushi init
13081 0x76, // push0
13082 0x38, PATCH_SELECTOR16(approachVerbs), // pushi approachVerbs
13083 0x78, // push1
13084 0x39, 0x04, // pushi 4d
13085 0x85, 0x01, // lat temp[1] (accumulate the object)
13086 0x4a, PATCH_UINT16(0x000a), // send 10d
13087 0x33, 0xe9, // jmp -23d [loop]
13088
13089 0x33, 0x33, // jmp 51d [skip subroutine declaration]
13090 0x38, PATCH_SELECTOR16(obstacles), // pushi obstacles (look up "obstacles", might be null)
13091 0x76, // push0
13092 0x81, 0x02, // lag global[2]
13093 0x4a, PATCH_UINT16(0x0004), // send 4d
13094 0x31, 0x11, // bnt 17d [skip disposal and nulling]
13095 0x38, PATCH_SELECTOR16(dispose), // pushi dispose
13096 0x76, // push0
13097 0x4a, PATCH_UINT16(0x0004), // send 4d ((global[2] obstacles?) dispose:)
13098 //
13099 0x38, PATCH_SELECTOR16(obstacles), // pushi obstacles (null the "obstacles" property)
13100 0x78, // push1
13101 0x76, // push0
13102 0x81, 0x02, // lag global[2]
13103 0x4a, PATCH_UINT16(0x0006), // send 6d
13104 //
13105 0x38, PATCH_SELECTOR16(addObstacle), // pushi addObstacle
13106 0x78, // push1
13107 0x32, PATCH_UINT16(0x020f), // jmp 527d [3rd polygon type, init, and push]
13108 // (That will jmp back here)
13109 0x81, 0x02, // lag global[2]
13110 0x4a, PATCH_UINT16(0x0006), // send 6d
13111 //
13112 0x38, PATCH_SELECTOR16(init), // pushi init
13113 0x76, // push0
13114 0x72, PATCH_GETORIGINALUINT16(605), // lofsa secritExit
13115 0x4a, PATCH_UINT16(0x0004), // send 4d
13116 0x48, // ret
13117
13118 0x33, 0x07, // jmp 7d [skip waste bytes, to (global[78] add: steps1)]
13119 0x5c, // selfID (waste 1 byte)
13120 PATCH_ADDTOOFFSET(+494), // ...
13121 0x76, // push0 (0 call args, clobber the old secritExit init)
13122 0x40, PATCH_UINT16(0xfdd6), PATCH_UINT16(0x0000), // call 0d [-554] (the subroutine does everything)
13123 0x32, PATCH_UINT16(0x00ee), // jmp 238d [end the cond]
13124 0x5c, // selfID (waste 1 byte)
13125 PATCH_ADDTOOFFSET(+4), // ...
13126 PATCH_ADDTOOFFSET(+228), // ... (3rd polygon type, init, and push)
13127 0x32, PATCH_UINT16(0xfd0a), // jmp -758d [back into the subroutine]
13128 0x35, 0x00, // ldi 0 (erase 2 bytes to keep disasm aligned)
13129 PATCH_END
13130 };
13131
13132 // Applies to at least: English CD
13133 // Responsible method: sBarrelMove::changeState(2) in script 340
13134 static const uint16 qfg4GuildWalkCDSignature3[] = {
13135 SIG_MAGICDWORD,
13136 0x30, SIG_UINT16(0x0032), // bnt 50d [next case]
13137 0x35, 0x02, // ldi 2d (case 2 label)
13138 SIG_ADDTOOFFSET(+26), // ... (register branch and derelict say())
13139 SIG_ADDTOOFFSET(+19), // ... (else, the rest of case 2 is a necessary say())
13140 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch]
13141 SIG_END
13142 };
13143
13144 static const uint16 qfg4GuildWalkCDPatch3[] = {
13145 0x31, 0x15, // bnt 21d [next case]
13146 0x38, PATCH_SELECTOR16(say), // pushi say
13147 0x39, 0x05, // pushi 5d
13148 0x39, 0x06, // pushi 6d
13149 0x39, 0x04, // pushi 4d
13150 0x39, 0x13, // pushi 19d
13151 0x76, // push0
13152 0x7c, // pushSelf
13153 0x81, 0x5b, // lag global[91]
13154 0x4a, PATCH_UINT16(0x000e), // send 14d
13155 0x32, PATCH_GETORIGINALUINT16ADJUST(51, +30), // jmp ?? [end the switch]
13156
13157 0x3c, // dup (case 8 prologue)
13158 0x35, 0x08, // ldi 8d
13159 0x1a, // eq?
13160 0x31, 0x06, // bnt 6d [next case]
13161 0x76, // push0 (0 call args)
13162 0x40, PATCH_UINT16(0xf592), PATCH_UINT16(0x0000), // call [-2670], 0d (patch 2's subroutine)
13163 0x33, 0x10, // jmp 16d [skip waste bytes]
13164 PATCH_END // (don't end the switch, keep testing cases)
13165 };
13166
13167 // Applies to at least: English floppy, German floppy
13168 // Responsible method: sBarrelMove::changeState(2) in script 340
13169 static const uint16 qfg4GuildWalkFloppySignature3[] = {
13170 SIG_MAGICDWORD,
13171 0x30, SIG_UINT16(0x0032), // bnt 50d [next case]
13172 0x35, 0x02, // ldi 2d (case 2 label)
13173 SIG_ADDTOOFFSET(+26), // ... (register branch and derelict say())
13174 SIG_ADDTOOFFSET(+19), // ... (else, the rest of case 2 is a necessary say())
13175 0x32, SIG_ADDTOOFFSET(+2), // jmp ?? [end the switch]
13176 SIG_END
13177 };
13178
13179 static const uint16 qfg4GuildWalkFloppyPatch3[] = {
13180 0x31, 0x15, // bnt 21d [next case]
13181 0x38, PATCH_SELECTOR16(say), // pushi say
13182 0x39, 0x05, // pushi 5d
13183 0x39, 0x06, // pushi 6d
13184 0x39, 0x04, // pushi 4d
13185 0x39, 0x13, // pushi 19d
13186 0x76, // push0
13187 0x7c, // pushSelf
13188 0x81, 0x5b, // lag global[91]
13189 0x4a, PATCH_UINT16(0x000e), // send 14d
13190 0x32, PATCH_GETORIGINALUINT16ADJUST(51, +30), // jmp ?? [end the switch]
13191
13192 0x3c, // dup (case 8 prologue)
13193 0x35, 0x08, // ldi 8d
13194 0x1a, // eq?
13195 0x31, 0x06, // bnt 6d [next case]
13196 0x76, // push0 (0 call args)
13197 0x40, PATCH_UINT16(0xf5a8), PATCH_UINT16(0x0000), // call [-2648], 0d (patch 2's subroutine)
13198 0x33, 0x10, // jmp 16d [skip waste bytes]
13199 PATCH_END // (don't end the switch, keep testing cases)
13200 };
13201
13202 // Rations are not properly decremented by daily scheduled meal consumption.
13203 // Rations are consumed periodically as time advances. If rations are the
13204 // active inventory item when the last of them is eaten, that icon will persist
13205 // in the verb bar.
13206 //
13207 // We make room by consolidating common gloryMessager::say() args in a
13208 // subroutine and cache values with temp variables. We add code to clean up
13209 // the verb bar when rations are exhausted (test if rations were the active
13210 // item, advance the cursor, hide the bar's invItem icon) - similar to the
13211 // localproc in script 16, called by combinable items to remove themselves.
13212 //
13213 // Applies to at least: English CD, English floppy, German floppy
13214 // Responsible method: hero::eatMeal() in script 28
13215 // Fixes bug: #10772
13216 static const uint16 qfg4LeftoversSignature[] = {
13217 0x3f, 0x01, // link 1d
13218 SIG_ADDTOOFFSET(+9), // ...
13219 SIG_MAGICDWORD,
13220 0xe1, 0x88, // -ag global[136] (digest a preemptively eaten meal)
13221 0x35, 0x01, // ldi 1d
13222 SIG_ADDTOOFFSET(+238), // ...
13223 0x85, 0x00, // lat temp[0] (eaten, unset flags if true)
13224 SIG_END
13225 };
13226
13227 static const uint16 qfg4LeftoversPatch[] = {
13228 0x3f, 0x03, // link 3d (3 temp vars)
13229
13230 PATCH_ADDTOOFFSET(+15), // (cond 1, preemptively eaten meals)
13231 0x32, PATCH_UINT16(0x00bb), // jmp 187d [end the cond]
13232
13233 // (cond 2)
13234 0x39, PATCH_SELECTOR8(at), // pushi at
13235 0x78, // push1
13236 0x39, 0x04, // pushi 4d (itemId 4, theRations)
13237 0x81, 0x09, // lag global[9] (gloryInv)
13238 0x4a, PATCH_UINT16(0x0006), // send 6d
13239 0xa5, 0x01, // sat temp[1] (theRations)
13240
13241 0x38, PATCH_SELECTOR16(amount), // pushi amount
13242 0x76, // push0
13243 0x4a, PATCH_UINT16(0x0004), // send 4d (theRations amount:)
13244 0xa5, 0x02, // sat temp[2] (amount)
13245
13246 0x31, 0x50, // bnt 80d [next condition]
13247
13248 0x38, PATCH_SELECTOR16(amount), // pushi amount
13249 0x78, // push1
13250 0xed, 0x02, // -st temp[2] (amount)
13251 0x85, 0x01, // lat temp[1] (theRations)
13252 0x4a, PATCH_UINT16(0x0006), // send 6d (decrement amount)
13253
13254 0x85, 0x02, // lat temp[2] (amount)
13255 0x2f, 0x3b, // bt 59d [skip exhausted item removal]
13256
13257 0x38, PATCH_SELECTOR16(owner), // pushi owner
13258 0x78, // push1
13259 0x76, // push0
13260 0x85, 0x01, // lat temp[1] (theRations)
13261 0x4a, PATCH_UINT16(0x0006), // send 6d
13262
13263 0x38, PATCH_SELECTOR16(curInvIcon), // pushi curInvIcon
13264 0x76, // push0
13265 0x81, 0x45, // lag global[69] (mainIconBar)
13266 0x4a, PATCH_UINT16(0x0004), // send 4d
13267 0x8d, 0x01, // lst temp[1] (theRations)
13268 0x1a, // eq?
13269 0x31, 0x23, // bnt 35d [skip icon bar disabling]
13270
13271 0x38, PATCH_SELECTOR16(curInvIcon), // pushi curInvIcon
13272 0x78, // push1
13273 0x76, // push0
13274 0x38, PATCH_SELECTOR16(advanceCurIcon), // pushi advanceCurIcon
13275 0x76, // push0
13276 0x38, PATCH_SELECTOR16(disable), // pushi disable
13277 0x78, // push1
13278 0x39, 0x06, // pushi 6d
13279 0x81, 0x45, // lag global[69] (mainIconBar)
13280 0x4a, PATCH_UINT16(0x0010), // send 16d
13281
13282 0x38, PATCH_SELECTOR16(hide), // pushi hide
13283 0x76, // push0
13284 //
13285 0x7a, // push2 (2 call args)
13286 0x39, 0x24, // pushi 36d
13287 0x78, // push1
13288 0x43, 0x02, PATCH_UINT16(0x0004), // callk ScriptID, 4d (ScriptID 36 1, invItem)
13289 //
13290 0x4a, PATCH_UINT16(0x0004), // send 4d (invItem hide:)
13291 // (exhausted item removal end)
13292
13293 0x35, 0x01, // ldi 1d
13294 0xa5, 0x00, // sat temp[0] (eaten)
13295
13296 0x33, 0x54, // jmp 84d [end the cond]
13297
13298 // (cond 3)
13299 0x78, // push1 (1 call arg)
13300 0x39, 0x03, // pushi 3d ("hungry" flag)
13301 0x45, 0x04, PATCH_UINT16(0x0002), // callb [export 4 of script 0], 2d (test flag 3)
13302 0x31, 0x26, // bnt 38d [next condition]
13303 //
13304 0x38, PATCH_SELECTOR16(useStamina), // pushi useStamina
13305 0x7a, // push2
13306 0x39, 0x08, // pushi 8d
13307 0x76, // push0
13308 0x54, PATCH_UINT16(0x0008), // self 8d (hero useStamina: 8 0)
13309 //
13310 0x31, 0x09, // bnt 9d [hero dies]
13311 0x78, // push1 (1 call arg)
13312 0x39, 0x05, // pushi 5d (say cond:5, "You're starving.")
13313 0x41, 0x3a, PATCH_UINT16(0x0002), // call [58], 2d (gloryMessager say: 1 6 5 1 0 28)
13314 0x33, 0x36, // jmp 54d [end the cond]
13315 //
13316 0x39, 0x04, // pushi 4d (4 call args)
13317 0x39, 0x08, // pushi 8d
13318 0x39, 0x1c, // pushi 28d
13319 0x38, PATCH_UINT16(0x03e3), // pushi 995d
13320 0x78, // push1
13321 0x47, 0x1a, 0x00, PATCH_UINT16(0x0008), // calle [export 0 of script 26], 8d (hero dies)
13322 0x33, 0x25, // jmp 37d [end the cond]
13323
13324 // (cond 4)
13325 0x78, // push1 (1 call arg)
13326 0x7a, // push2 ("missed meal" flag)
13327 0x45, 0x04, PATCH_UINT16(0x0002), // callb [export 4 of script 0], 2d (test flag 2)
13328 0x31, 0x10, // bnt 16d [next condition]
13329 //
13330 0x78, // push1 (1 call arg)
13331 0x39, 0x03, // pushi 3d ("hungry" flag)
13332 0x45, 0x02, PATCH_UINT16(0x0002), // callb [export 2 of script 0], 2d (set flag 3)
13333 //
13334 0x78, // push1 (1 call arg)
13335 0x39, 0x06, // pushi 6d (say cond:6, "Really getting hungry.")
13336 0x41, 0x11, PATCH_UINT16(0x0002), // call [17], 2d (gloryMessager say: 1 6 6 1 0 28)
13337 0x33, 0x0d, // jmp 13d [end the cond]
13338
13339 // (cond else)
13340 0x78, // push1 (1 call arg)
13341 0x7a, // push2 ("missed meal" flag)
13342 0x45, 0x02, PATCH_UINT16(0x0002), // callb [export 2 of script 0], 2d (set flag 2)
13343 //
13344 0x78, // push1 (1 call arg)
13345 0x39, 0x04, // pushi 4d (say cond:4, "Get food soon.")
13346 0x41, 0x02, PATCH_UINT16(0x0002), // call [2], 2d (gloryMessager say: 1 6 4 1 0 28)
13347
13348 // (cond end)
13349
13350 0x33, 0x14, // jmp 20d [skip subroutine declaration]
13351 0x38, PATCH_SELECTOR16(say), // pushi say
13352 0x39, 0x06, // pushi 6d
13353 0x78, // push1 (noun)
13354 0x39, 0x06, // pushi 6d (verb)
13355 0x8f, 0x01, // lsp param[1] (cond varies)
13356 0x78, // push1 (seq)
13357 0x76, // push0 (caller)
13358 0x39, 0x1c, // pushi 28d (message pool)
13359 0x81, 0x5b, // lag global[91] (gloryMessager say: 1 6 ? 1 0 28)
13360 0x4a, PATCH_UINT16(0x0010), // send 16d
13361 0x48, // ret
13362
13363 0x33, 0x16, // jmp 22d [skip waste bytes]
13364 0x35, 0x00, // ldi 0 (erase 2 bytes to keep disasm aligned)
13365 PATCH_END
13366 };
13367
13368 // The runes puzzle in room 800 often rejects the correct answer. When a letter
13369 // is selected it turns red but it's not applied until after a 30 tick delay
13370 // with no visual indicator. If a letter is clicked during that delay then the
13371 // previous letter is silently skipped, which is common since the correct
13372 // answer contains the same letter consecutively.
13373 //
13374 // There are two approaches to fixing this: remove the delay or disable input
13375 // during it. We do both. Letters are now applied as soon as they turn red, but
13376 // the delay prevented the puzzle from ending abruptly, and so we still pause
13377 // after the puzzle is solved but disable input. This preserves the puzzle's
13378 // external behavior while making it impossible to click too fast.
13379 //
13380 // This bug is in all versions but the CD version exacerbates it with its broken
13381 // dial. Sierra upgraded the Cycle classes which changed the behavior the dial
13382 // depends on. CycleTo no longer supports wrapping around cel ranges and so the
13383 // dial can't move from "C" to "O" (cels 7 to 0) and doesn't spin around when
13384 // selecting the same letter twice as it did in floppy.
13385 //
13386 // Applies to: All versions
13387 // Responsible methods: proc_58 in script 801, runePuz:handleEvent, sTurnTheDial:changeState
13388 // Fixes bug: #10965
13389 static const uint16 qfg4RunesPuzzleSignature1[] = {
13390 SIG_MAGICDWORD,
13391 0x38, SIG_UINT16(0x0163), // pushi 0163
13392 0x45, 0x02, SIG_UINT16(0x0002), // callb proc0_2 [ set flag 355, puzzle is solved ]
13393 0x38, SIG_SELECTOR16(dispose), // pushi dispose
13394 0x76, // push0
13395 0x72, SIG_UINT16(0x0010), // lofsa runesPuz
13396 0x4a, SIG_UINT16(0x0004), // send 04 [ runesPuz dispose: ]
13397 SIG_END
13398 };
13399
13400 static const uint16 qfg4RunesPuzzlePatch1[] = {
13401 PATCH_ADDTOOFFSET(+7),
13402 0x32, PATCH_UINT16(0x0007), // jmp 0007 [ don't exit puzzle immediately ]
13403 PATCH_END
13404 };
13405
13406 static const uint16 qfg4RunesPuzzleSignature2[] = {
13407 // runePuz:handleEvent
13408 0x63, SIG_ADDTOOFFSET(+1), // pToa register [ always zero, the following code is unused ]
13409 SIG_MAGICDWORD,
13410 0x31, 0x21, // bnt 21 [ handle mouse/key down events ]
13411 0x39, 0x04, // pushi 04
13412 0x39, SIG_ADDTOOFFSET(+1), // pushi type
13413 0x76, // push0
13414 0x87, 0x01, // lap 01
13415 0x4a, SIG_UINT16(0x0004), // send 04 [ event type? ]
13416 SIG_ADDTOOFFSET(+495),
13417 0x39, SIG_SELECTOR8(claimed), // pushi claimed
13418 SIG_ADDTOOFFSET(+852),
13419 // sTurnTheDial:changeState
13420 0x30, SIG_UINT16(0x0112), // bnt 0112 [ state 2 ]
13421 SIG_ADDTOOFFSET(+268),
13422 0x35, 0x1e, // ldi 1e
13423 0x65, SIG_ADDTOOFFSET(+1), // aTop ticks
13424 0x33, 0x20, // jmp 20 [ end of method ]
13425 0x3c, // dup
13426 0x35, 0x02, // ldi 02
13427 0x1a, // eq?
13428 0x31, 0x1a, // bnt 1a [ end of method ]
13429 0x76, // push0
13430 0x40, SIG_ADDTOOFFSET(+2), // call proc_58 [ apply letter to puzzle ]
13431 SIG_UINT16(0x0000),
13432 0x38, SIG_SELECTOR16(canControl), // pushi canControl
13433 0x78, // push1
13434 0x78, // push1
13435 0x51, SIG_ADDTOOFFSET(+1), // class User
13436 0x4a, SIG_UINT16(0x0006), // send 06 [ User canControl: 1 (unnecessary) ]
13437 0x38, SIG_SELECTOR16(dispose), // pushi dispose
13438 0x76, // push0
13439 0x72, SIG_ADDTOOFFSET(+2), // lofsa sTurnTheDial
13440 0x4a, SIG_UINT16(0x0004), // send 04 [ sTurnTheDial dispose: (unnecessary) ]
13441 SIG_END
13442 };
13443
13444 static const uint16 qfg4RunesPuzzlePatch2[] = {
13445 // runePuz:handleEvent
13446 0x78, // push1
13447 0x38, PATCH_UINT16(0x0163), // pushi 0163
13448 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_3 [ is puzzle solved? ]
13449 0x31, 0x1b, // bnt 1b [ handle mouse/key down events ]
13450 0x32, PATCH_UINT16(0x01f0), // jmp 01f0 [ ignore events if puzzle is solved ]
13451 PATCH_ADDTOOFFSET(+1350),
13452 // sTurnTheDial:changeState
13453 0x30, PATCH_UINT16(0x0123), // bnt 0123 [ state 2 ]
13454 PATCH_ADDTOOFFSET(+268),
13455 0x76, // push0
13456 0x40, PATCH_GETORIGINALUINT16ADJUST(+1648, +12), // call proc_58 [ apply letter to puzzle ]
13457 PATCH_UINT16(0x0000),
13458 0x39, 0x01, // push1
13459 0x38, PATCH_UINT16(0x0163), // pushi 0163
13460 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_3 [ is puzzle solved? ]
13461 0x31, 0x10, // bnt 10 [ end of method ]
13462 0x35, 0x1e, // ldi 1e
13463 0x65, PATCH_GETORIGINALBYTE(+1637), // aTop ticks [ pause 30 ticks before exiting puzzle ]
13464 0x33, 0x0a, // jmp 0a [ end of method ]
13465 0x38, PATCH_SELECTOR16(dispose), // pushi dispose
13466 0x76, // push0
13467 0x72, PATCH_UINT16(0x0010), // lofsa runesPuz
13468 0x4a, PATCH_UINT16(0x0004), // send 04 [ runesPuz dispose: ]
13469 0x3a, // toss
13470 0x48, // ret
13471 PATCH_END
13472 };
13473
13474 // The Domovoi in room 320 has a complex bug in the CD version. If you don't
13475 // talk to him before Bella wakes you up then you can't get the doll and the
13476 // game can't be completed. The event logic was changed in the floppy patch and
13477 // again in the CD version, which introduced the bug. Working backwards...
13478 //
13479 // To get the doll from the inn's cabinet:
13480 // - It must be late at night
13481 // - Inn event 15 has occurred or is occurring
13482 //
13483 // To trigger inn event 15 ("You see that the Domovoi is here again"):
13484 // - It must be late at night
13485 // - You saved the monastery's Domovoi
13486 // - You clicked Talk on the Domovoi during inn event 3 (new requirement in CD)
13487 // - You didn't just enter from your room and hear crying
13488 //
13489 // To trigger inn event 3 ("You have the feeling you are being watched"):
13490 // - It must be late at night
13491 // - You haven't already clicked Talk on the Domovoi during inn event 3
13492 // - You haven't been woken by Bella (new requirement in CD)
13493 // - You didn't just enter from your room and hear crying
13494 //
13495 // The two new requirements create an unwinnable state. Once Bella wakes you,
13496 // event 3 is no longer possible, cascading to event 15 and the doll. This also
13497 // prevents the Domovoi from appearing in your room as that requires talking
13498 // to him during event 3. Putting it all together, the new Bella requirement's
13499 // only effect is to suppress a mandatory event, and so it is safe to remove.
13500 //
13501 // Applies to: English CD
13502 // Responsible methods: rm320:init, heroTeller:respond
13503 // Fixes bug: #10978
13504 static const uint16 qfg4DomovoiInnSignature[] = {
13505 SIG_MAGICDWORD,
13506 0x78, // push1
13507 0x38, SIG_UINT16(0x0088), // pushi 0088
13508 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 [ is flag 136 set? (has Bella woken you up?) ]
13509 0x18, // not
13510 0x31, // bnt [ skip inn event 3 ]
13511 SIG_END
13512 };
13513
13514 static const uint16 qfg4DomovoiInnPatch[] = {
13515 0x32, PATCH_UINT16(0x0008), // jmp 0008 [ skip flag 136 check ]
13516 PATCH_END
13517 };
13518
13519 // During the final battle with Ad Avis his initial timer is never stopped,
13520 // reducing the intended time the player has to complete the sequence by more
13521 // than half, and bringing Ad Avis back to life after he's killed.
13522 //
13523 // sTimeItOut state 0 sets a timeout when the battle starts. Its length depends
13524 // on the detected cpu speed and game version. In the floppy versions this was
13525 // a minimum of 400 seconds, which is so long that it masked the bug, but in CD
13526 // it was reduced to 20 seconds. This is supposed to be how long the player has
13527 // to tell the joke, after which sUltimakeJoke sets a second timeout in which
13528 // the character-specific actions are to be done, but sTimeItOut finishes first
13529 // and forces the player to complete both phases during the first shorter one.
13530 // When Ad Avis is killed his death scripts only stop sUltimakeJoke, as they
13531 // don't expect sTimeItOut to be running, and so when sTimeItOut times out it
13532 // kills the player unless the death script has already called avis:dispose.
13533 //
13534 // We fix this by patching sTimeItOut state 1 to abort the script if the joke
13535 // has been told. This is equivalent to the NRS patch that ships with the GOG
13536 // version, which disposes sTimeItOut when telling the joke, and so this patch
13537 // is applied to all versions except that one.
13538 //
13539 // Applies to: All versions
13540 // Responsible method: sTimeItOut:changeState(1)
13541 // Fixes bug: #10844
13542 static const uint16 qfg4AdAvisTimeoutSignature[] = {
13543 0x30, SIG_UINT16(0x002c), // bnt 002c [ state 1 ]
13544 SIG_ADDTOOFFSET(+0x29),
13545 SIG_MAGICDWORD,
13546 0x32, SIG_UINT16(0x00ae), // jmp 00ae [ end of method ]
13547 0x3c, // dup
13548 0x35, 0x01, // ldi 01
13549 0x1a, // eq?
13550 0x30, SIG_UINT16(0x0027), // bnt 0027 [ state 2 ]
13551 SIG_END
13552 };
13553
13554 static const uint16 qfg4AdAvisTimeoutPatch[] = {
13555 0x30, PATCH_UINT16(0x0029), // bnt 0029 [ state 1 ]
13556 PATCH_ADDTOOFFSET(+0x29),
13557 0x3c, // dup
13558 0x35, 0x01, // ldi 01
13559 0x1a, // eq?
13560 0x31, 0x2b, // bnt 2b [ state 2 ]
13561 0x83, 0x04, // lal 04 [ has joke been told? ]
13562 0x2f, 0x27, // bt 27 [ abort script if joke has been told ]
13563 PATCH_END
13564 };
13565
13566 // During the final battle with Ad Avis if the player casts a non-fatal spell at
13567 // him then they can't cast any more spells. Instead they receive a message
13568 // about being too busy or it not being a good place and must wait to die.
13569 // Another symptom of this bug is that fighters and thieves don't get to see
13570 // the staff transform if they've previously thrown a weapon.
13571 //
13572 // SpellItem:doVerb(4) determines if a spell is allowed. If hero:view is the
13573 // wrong value then it says "This isn't a good place..." and if the room has
13574 // a script it says "You're too busy...". avis:getHurt breaks one or both of
13575 // these conditions by setting the room script to sMessages. If the game speed
13576 // is set to less than high then avis:getHurt runs while the "project" room
13577 // script is animating hero. Setting the room script to sMessages interrupts
13578 // this and hero is left on view 14, breaking the first spell condition. Even
13579 // if the game speed is set to high and hero's animation completes, sMessages
13580 // fails to dispose itself, leaving it as the room script when it's complete
13581 // and breaking the second spell condition.
13582 //
13583 // We fix this by reassigning sMessages from the room's script to midBlast, an
13584 // arbitrary Prop that no scripts depend on. project is no longer interrupted,
13585 // hero's animation completes at all speeds, and it no longer matters that
13586 // sMessage fails to dispose itself. Due to script changes, this patch is only
13587 // applied once to floppy and twice to CD.
13588 //
13589 // We also include a version of this for the offsets in the NRS patch, which is
13590 // important as that ships with the GOG version.
13591 //
13592 // Applies to: All versions
13593 // Responsible method: avis:getHurt
13594 // Fixes bug: #10835
13595 static const uint16 qfg4AdAvisSpellsFloppySignature[] = {
13596 SIG_MAGICDWORD,
13597 0x72, SIG_UINT16(0x0096), // lofsa sMessages
13598 0x36, // push
13599 0x81, 0x02, // lag 02
13600 0x4a, SIG_UINT16(0x0006), // send 06 [ rm730 setScript: sMessages ]
13601 SIG_END
13602 };
13603
13604 static const uint16 qfg4AdAvisSpellsFloppyPatch[] = {
13605 0x74, PATCH_ADDTOOFFSET(+2), // lofss sMessages
13606 0x72, PATCH_UINT16(0x0668), // lofsa midBlast
13607 SIG_END
13608 };
13609
13610 static const uint16 qfg4AdAvisSpellsCDSignature[] = {
13611 SIG_MAGICDWORD,
13612 0x72, SIG_UINT16(0x00a6), // lofsa sMessages
13613 0x36, // push
13614 0x81, 0x02, // lag 02
13615 0x4a, SIG_UINT16(0x0006), // send 06 [ rm730 setScript: sMessages ]
13616 SIG_END
13617 };
13618
13619 static const uint16 qfg4AdAvisSpellsCDPatch[] = {
13620 0x74, PATCH_ADDTOOFFSET(+2), // lofss sMessages
13621 0x72, PATCH_UINT16(0x06b6), // lofsa midBlast
13622 SIG_END
13623 };
13624
13625 static const uint16 qfg4AdAvisSpellsNrsSignature[] = {
13626 SIG_MAGICDWORD,
13627 0x72, SIG_UINT16(0x00a8), // lofsa sMessages
13628 0x36, // push
13629 0x81, 0x02, // lag 02
13630 0x4a, SIG_UINT16(0x0006), // send 06 [ rm730 setScript: sMessages ]
13631 SIG_END
13632 };
13633
13634 static const uint16 qfg4AdAvisSpellsNrsPatch[] = {
13635 0x74, PATCH_ADDTOOFFSET(+2), // lofss sMessages
13636 0x72, PATCH_UINT16(0x06b8), // lofsa midBlast
13637 SIG_END
13638 };
13639
13640 // If the magic user defeats Ad Avis with the game speed set to less than high
13641 // then they aren't allowed to cast the final summon staff spell and complete
13642 // the game. Instead they receive "You're too busy to cast a spell right now."
13643 //
13644 // Spells can't be cast if a room script is set, as described in the above patch
13645 // notes. When Ad Avis is killed, avis:getHurt sets hero's view and loop before
13646 // running sAdavisDies. If the game speed isn't set to high then the "project"
13647 // script that deployed the final projectile spell is still running and waiting
13648 // on hero's animation to complete. By changing the view and loop, avis:getHurt
13649 // prevents project from advancing to its next state and completing, leaving it
13650 // stuck as the room script and blocking the final spell.
13651 //
13652 // We can't prevent project from being the room script as that's game-wide
13653 // behavior, and we can't prevent it from being interrupted since avis:getHurt
13654 // needs to set hero's final view/loop, but we can still fix the bug by setting
13655 // sAdavisDies as the room's script instead of hero's. This disposes project if
13656 // it's still running and guarantees that the room script is cleared since
13657 // sAdavisDies always disposes of itself.
13658 //
13659 // Applies to: All versions
13660 // Responsible method: avis:getHurt
13661 // Fixes bug: #10835
13662 static const uint16 qfg4AdAdvisLastSpellSignature[] = {
13663 0x38, SIG_SELECTOR16(setScript), // pushi setScript
13664 0x78, // push1
13665 0x72, SIG_ADDTOOFFSET(+2), // lofsa sAdavisDies
13666 SIG_MAGICDWORD,
13667 0x36, // push
13668 0x81, 0x00, // lag 00
13669 0x4a, SIG_UINT16(0x0006), // send 06 [ hero setScript: sAdavisDies ]
13670 SIG_END
13671 };
13672
13673 static const uint16 qfg4AdAdvisLastSpellPatch[] = {
13674 PATCH_ADDTOOFFSET(+8),
13675 0x81, 0x02, // lag 02 [ rm730 ]
13676 SIG_END
13677 };
13678
13679 // When throwing a weapon or casting a spell at Ad Avis in room 730, sMessages
13680 // tests the projectile type incorrectly and transposes the message responses.
13681 //
13682 // Applies to: All versions
13683 // Responsible method: sMessages:changeState(2)
13684 // Fixes bug: #10989
13685 static const uint16 qfg4AdAvisMessageSignature[] = {
13686 0x8b, SIG_MAGICDWORD, 0x01, // lsl 01 [ 0 if weapon thrown, else a spell ]
13687 0x35, 0x00, // ldi 00
13688 0x1a, // eq?
13689 SIG_END
13690 };
13691
13692 static const uint16 qfg4AdAvisMessagePatch[] = {
13693 PATCH_ADDTOOFFSET(+4),
13694 0x1c, // ne?
13695 PATCH_END
13696 };
13697
13698 // Throwing a rock or dagger at Ad Avis after telling the joke kills him.
13699 // avis:getHurt fails to test the projectile type correctly in CD, or at all in
13700 // floppy, and so all versions mistake this for casting a spell with the staff.
13701 //
13702 // We fix this by testing the projectile type and not allowing a thrown weapon
13703 // to kill Ad Avis. This replaces an unnecessary hero:script test.
13704 //
13705 // Applies to: All versions
13706 // Responsible method: avis:getHurt
13707 // Fixes bug: #10989
13708 static const uint16 qfg4AdAvisThrowWeaponSignature[] = {
13709 SIG_MAGICDWORD,
13710 0x38, SIG_SELECTOR16(script), // pushi script
13711 0x76, // push0
13712 0x81, 0x00, // lag 00
13713 0x4a, SIG_UINT16(0x0004), // send 04 [ hero script? ]
13714 0x18, // not
13715 0x30, SIG_ADDTOOFFSET(+2), // bnt [ projectile doesn't kill ad avis ]
13716 0x39, SIG_SELECTOR8(view), // pushi view
13717 SIG_END
13718 };
13719
13720 static const uint16 qfg4AdAvisThrowWeaponPatch[] = {
13721 0x83, 0x01, // lal 01 [ 0 if weapon thrown, else a spell ]
13722 0x33, 0x06, // jmp 06 [ throwing a weapon doesn't kill ad avis ]
13723 PATCH_END
13724 };
13725
13726 // When a fighter or paladin selects the staff in the final battle with Ad Avis
13727 // after throwing a rock or dagger they enter an infinite animation loop due to
13728 // not clearing hero:cycler. Multiple bugs in this room prevented getting this
13729 // far, but we fixed those, so we also fix this by clearing the cycler.
13730 //
13731 // Applies to: All versions
13732 // Responsible method: sDoTheStaff:changeState(3)
13733 // Fixes bug: #10835
13734 static const uint16 qfg4FighterSpearSignature[] = {
13735 0x39, SIG_SELECTOR8(view), // pushi view [ start of fighter code, same as paladin ]
13736 SIG_ADDTOOFFSET(0x3e),
13737 0x3c, // dup
13738 0x35, SIG_MAGICDWORD, 0x03, // ldi 03
13739 0x1a, // eq? [ is paladin? (last condition so always true) ]
13740 0x30, SIG_UINT16(0x0022), // bnt 0022
13741 0x39, SIG_SELECTOR8(view), // pushi view
13742 0x78, // push1
13743 0x39, 0x0a, // pushi 0a
13744 0x38, SIG_SELECTOR16(setLoop), // pushi setLoop
13745 0x7a, // push2
13746 0x76, // push0
13747 0x78, // push1
13748 0x38, SIG_SELECTOR16(setCel), // pushi setCel
13749 SIG_ADDTOOFFSET(+13),
13750 0x4a, SIG_UINT16(0x001c), // send 1c [ hero view: 10 setLoop 0 1 setCel: 0 ... ]
13751 SIG_END
13752 };
13753
13754 static const uint16 qfg4FighterSpearPatch[] = {
13755 0x33, 0x3e, // jmp 3e [ use patched paladin code for fighter ]
13756 PATCH_ADDTOOFFSET(0x3e),
13757 0x39, PATCH_SELECTOR8(view), // pushi view
13758 0x78, // push1
13759 0x39, 0x0a, // pushi 0a
13760 0x38, PATCH_SELECTOR16(setLoop), // pushi setLoop
13761 0x7a, // push2
13762 0x76, // push0
13763 0x78, // push1
13764 0x38, PATCH_SELECTOR16(setCel), // pushi setCel
13765 0x39, 0x01, // pushi 01
13766 0x39, 0x00, // pushi 00
13767 0x38, PATCH_SELECTOR16(setCycle), // pushi setCycle
13768 PATCH_ADDTOOFFSET(+13),
13769 0x4a, PATCH_UINT16(0x0022), // send 22 [ hero view: 10 setLoop 0 1 setCel: 0 setCycle: 0 ... ]
13770 PATCH_END
13771 };
13772
13773 // Clicking Do on the inn door in room 260 from certain coordinates crashes the
13774 // CD version. This is one of several related crashes where the Grooper or
13775 // Grycler classes send a selector to a non-object in only the CD version.
13776 //
13777 // The inn door script isn't buggy, and neither are Grooper or Grycler. Instead,
13778 // Sierra "upgraded" the core Cycle classes in the CD version with drastically
13779 // different behavior after the game was already written for the first ones.
13780 // It's unclear what they were attempting to accomplish, but the conspicuous
13781 // regressions include hero stuttering when walking on every screen, the runes
13782 // dial refusing to spin a full rotation, random crashes at the inn door and
13783 // on the slippery path in room 800, and probably other problems. Meanwhile
13784 // GK1, a relatively stable SCI32 game released at the same time, used the same
13785 // Cycle classes in all its versions as QFG4 floppy without motion problems.
13786 //
13787 // The crashes result from complex motion edge cases but involve hero ending up
13788 // without a cycler at the wrong moment. These can be avoided by adding a call
13789 // to hero:normalize to reset a lot of state and set hero:cycler to StopWalk
13790 // and hero:looper to stopGroop. This is a bit of a kitchen-sink solution but
13791 // it does the job without side effects and only requires 4 bytes.
13792 //
13793 // We prevent the inn door crash by calling hero:normalize in sInInnDoor.
13794 //
13795 // Applies to: English CD
13796 // Responsible method: sInInnDoor:changeState(1)
13797 // Fixes bug: #10760
13798 static const uint16 qfg4InnDoorCDSignature[] = {
13799 0x30, SIG_MAGICDWORD, // bnt 000e [ state 2 ]
13800 SIG_UINT16(0x000e),
13801 0x38, SIG_UINT16(0x0111), // pushi setHeading [ hard-coded for CD ]
13802 0x7a, // push2
13803 0x76, // push0
13804 0x7c, // pushSelf
13805 0x81, 0x00, // lag 00
13806 0x4a, SIG_UINT16(0x0008), // send 08 [ hero setHeading: 0 self ]
13807 0x32, SIG_UINT16(0x00c3), // jmp 00c3 [ end of method ]
13808 SIG_END
13809 };
13810
13811 static const uint16 qfg4InnDoorCDPatch[] = {
13812 0x31, 0x0f, // bnt 0f [ state 2 ]
13813 0x38, PATCH_SELECTOR16(normalize), // pushi normalize
13814 0x76, // push0
13815 0x38, PATCH_UINT16(0x0111), // pushi setHeading [ hard-coded for CD ]
13816 0x7a, // push2
13817 0x76, // push0
13818 0x7c, // pushSelf
13819 0x81, 0x00, // lag 00
13820 0x4a, PATCH_UINT16(0x000c), // send 0c [ hero normalize: setHeading: 0 self ]
13821 PATCH_END
13822 };
13823
13824 // In room 800, at the start of the game, automatically sliding down the top of
13825 // the slope can crash the CD version in Grooper:doit. See the inn door patch
13826 // above for details on these motion regressions and their solution.
13827 //
13828 // We fix this by calling hero:normalize at the start of sFallsBackSide to reset
13829 // hero's state before starting the motion sequence.
13830 //
13831 // This patch is not applied to the NRS fan-patch included in the GOG version.
13832 // It fixes this bug by adding a spin loop delay that's relative to game speed
13833 // to sFallsBackSide.
13834 //
13835 // Applies to: English CD
13836 // Responsible method: sFallsBackSide:changeState(0)
13837 // Fixes bug: #9801
13838 static const uint16 qfg4SlidingDownSlopeCDSignature[] = {
13839 0x87, 0x01, // lap 01
13840 0x65, 0x16, // aTop state
13841 0x36, // push
13842 0x3c, // dup
13843 0x35, SIG_MAGICDWORD, 0x00, // ldi 00
13844 0x1a, // eq?
13845 0x31, 0x30, // bnt 30 [ state 1 ]
13846 SIG_ADDTOOFFSET(+42),
13847 0x4a, SIG_UINT16(0x0014), // send 14 [ hero: setStep: 1 1 ... ]
13848 SIG_END
13849 };
13850
13851 static const uint16 qfg4SlidingDownSlopeCDPatch[] = {
13852 PATCH_ADDTOOFFSET(+5),
13853 0x2f, 0x34, // bt 34 [ state 1 ]
13854 0x38, PATCH_SELECTOR16(normalize), // pushi normalize
13855 0x76, // push0
13856 PATCH_ADDTOOFFSET(+42),
13857 0x4a, PATCH_UINT16(0x0018), // send 18 [ hero: normalize: setStep: 1 1 ... ]
13858 PATCH_END
13859 };
13860
13861 // Walking around the base of the slippery slope in room 800 can crash the CD
13862 // version in either the Grooper or Grycler classes. See the inn door patch
13863 // above for details on these regressions and their solution.
13864 //
13865 // The script sSlippery runs when walking up the slope and sWalksDown runs when
13866 // walking down. Both are vulnerable to Grooper/Grycler crashes and both can be
13867 // fixed by adding hero:normalize calls.
13868 //
13869 // We also include a version of the sWalksDown patch for the instruction sizes
13870 // in the NRS patch, which is important as that ships with the GOG version.
13871 //
13872 // Applies to: English CD
13873 // Responsible methods: sSlippery:changeState(0), sWalksDown:changeState(0)
13874 // Fixes bug: #10747
13875 static const uint16 qfg4WalkUpSlopeCDSignature[] = {
13876 SIG_MAGICDWORD,
13877 0x38, SIG_UINT16(0x0142), // pushi setMotion [ hard-coded for CD ]
13878 0x78, // push1
13879 0x76, // push0
13880 SIG_ADDTOOFFSET(+8),
13881 0x4a, SIG_UINT16(0x000e), // send 0e [ hero setMotion: 0 ... ]
13882 SIG_END
13883 };
13884
13885 static const uint16 qfg4WalkUpSlopeCDPatch[] = {
13886 0x38, PATCH_SELECTOR16(normalize), // pushi normalize
13887 0x39, 0x00, // pushi 00
13888 PATCH_ADDTOOFFSET(+8),
13889 0x4a, PATCH_UINT16(0x000c), // send 0c [ hero normalize: ... ]
13890 PATCH_END
13891 };
13892
13893 static const uint16 qfg4WalkDownSlopeCDSignature[] = {
13894 0x3c, // dup
13895 0x35, SIG_MAGICDWORD, 0x00, // ldi 00
13896 0x1a, // eq?
13897 0x31, 0x1e, // bnt 1e [ state 1 ]
13898 0x38, SIG_UINT16(0x0218), // pushi handsOff [ hard-coded for CD ]
13899 SIG_ADDTOOFFSET(+15),
13900 0x4a, SIG_UINT16(0x0008), // send 08 [ hero setStep: ... ]
13901 SIG_END
13902 };
13903
13904 static const uint16 qfg4WalkDownSlopeCDPatch[] = {
13905 0x2f, 0x22, // bt 22 [ state 1 ]
13906 0x38, PATCH_SELECTOR16(normalize), // pushi normalize
13907 0x76, // push0
13908 PATCH_ADDTOOFFSET(+18),
13909 0x4a, PATCH_UINT16(0x000c), // send 0c [ hero normalize: setStep: ... ]
13910 PATCH_END
13911 };
13912
13913 static const uint16 qfg4WalkDownSlopeNrsSignature[] = {
13914 0x3c, // dup
13915 0x35, SIG_MAGICDWORD, 0x00, // ldi 00
13916 0x1a, // eq?
13917 0x30, SIG_UINT16(0x001f), // bnt 001f [ state 1 ]
13918 0x38, SIG_UINT16(0x0218), // pushi handsOff [ hard-coded for CD ]
13919 SIG_ADDTOOFFSET(+15),
13920 0x4a, SIG_UINT16(0x0008), // send 08 [ hero setStep: ... ]
13921 SIG_END
13922 };
13923
13924 static const uint16 qfg4WalkDownSlopeNrsPatch[] = {
13925 0x2e, PATCH_UINT16(0x0023), // bt 0023 [ state 1 ]
13926 0x38, PATCH_SELECTOR16(normalize), // pushi normalize
13927 0x76, // push0
13928 PATCH_ADDTOOFFSET(+18),
13929 0x4a, PATCH_UINT16(0x000c), // send 0c [ hero normalize: setStep: ... ]
13930 PATCH_END
13931 };
13932
13933 // The NRS fan-patch for wraiths has a bug which locks up the game. This occurs
13934 // when a wraith initializes while game time is greater than $7fff. The patch
13935 // throttles wraith:doit to execute no more than once per game tick, which it
13936 // does by storing the previous game time in a new local variable whose initial
13937 // value is zero. This technique is used in several patches but this one is
13938 // missing a call to Abs that the others have. Once game time reaches $8000 or
13939 // greater, the signed less-than test will always pass when the local variable
13940 // is zero, and wraith:doit won't execute.
13941 //
13942 // We fix this by changing the signed less-than comparison to unsigned.
13943 //
13944 // Applies to: English CD with NRS patches 53.HEP/SCR
13945 // Responsible method: wraith:doit
13946 // Fixes bug: #10711
13947 static const uint16 qfg4WraithLockupNrsSignature[] = {
13948 SIG_MAGICDWORD,
13949 0x89, 0x58, // lsg 58
13950 0x83, 0x04, // lal 04
13951 0x04, // sub
13952 0x36, // push
13953 0x35, 0x01, // ldi 01
13954 0x22, // lt? [ (gameTime - prevGameTime) < 1 ]
13955 SIG_END
13956 };
13957
13958 static const uint16 qfg4WraithLockupNrsPatch[] = {
13959 PATCH_ADDTOOFFSET(+8),
13960 0x2a, // ult?
13961 PATCH_END
13962 };
13963
13964 // The script that determines how much money a revenant has is missing the first
13965 // parameter to kRandom, which should be zero as it is with other monsters.
13966 // Instead of awarding the intended 15 to 40 kopeks, it always awards 15 and
13967 // reseeds the random number generator to 25.
13968 //
13969 // Applies to: All versions
13970 // Responsible method: sSearchMonster:changeState(1)
13971 // Fixes bug: #10966
13972 static const uint16 qfg4SearchRevenantSignature[] = {
13973 0x39, 0x0f, // pushi 0f
13974 0x78, // push1
13975 0x39, SIG_MAGICDWORD, 0x19, // pushi 19
13976 0x43, 0x5d, SIG_UINT16(0x0002), // callk Random 02
13977 0x02, // add [ 15 + Random 25 ]
13978 SIG_END
13979 };
13980
13981 static const uint16 qfg4SearchRevenantPatch[] = {
13982 0x39, 0x02, // pushi 02
13983 0x39, 0x0f, // pushi 0f
13984 0x39, 0x28, // pushi 28
13985 0x43, 0x5d, PATCH_UINT16(0x0004), // callk Random 04 [ Random 15 40 ]
13986 PATCH_END
13987 };
13988
13989 // During combat, if a rabbit is all the way to the right and attacks then it
13990 // won't make any more moves, forcing the player to run away to end the fight.
13991 // This is due to rabbitCombat failing to pass a caller to the rabbitAttack
13992 // script and so it gets stuck. We pass the missing "self" parameter.
13993 //
13994 // Applies to: All versions
13995 // Responsible method: rabbitCombat:changeState(1)
13996 // Fixes bug: #11000
13997 static const uint16 qfg4RabbitCombatSignature[] = {
13998 0x38, SIG_SELECTOR16(setScript), // pushi setScript
13999 0x78, // push1
14000 0x72, SIG_ADDTOOFFSET(+2), // lofsa rabbitAttack
14001 0X36, // push
14002 SIG_MAGICDWORD,
14003 0x54, SIG_UINT16(0x0006), // self 06 [ self setScript: rabbitAttack ]
14004 0x32, SIG_UINT16(0x014b), // jmp 014b
14005 SIG_END
14006 };
14007
14008 static const uint16 qfg4RabbitCombatPatch[] = {
14009 PATCH_ADDTOOFFSET(+3),
14010 0x7a, // push2
14011 0x74, PATCH_ADDTOOFFSET(+2), // lofss rabbitAttack
14012 0x7c, // pushSelf
14013 0x54, PATCH_UINT16(0x0008), // self 08 [ self setScript: rabbitAttack self ]
14014 PATCH_END
14015 };
14016
14017 // Attempting to open the monastery door in room 250 while Igor is present
14018 // randomly locks up the game. sHectapusDeath stands Igor up, but this can be
14019 // interrupted by sIgorCarves animating him at random intervals, leaving
14020 // sHectapusDeath stuck in handsOff mode.
14021 //
14022 // We fix this by first stopping sIgorCarves as other scripts in this room do.
14023 //
14024 // Applies to: All versions
14025 // Responsible method: sHectapusDeath:changeState(4)
14026 // Fixes bug: #10994
14027 static const uint16 qfg4HectapusDeathSignature[] = {
14028 0x30, SIG_UINT16(0x0027), // bnt 0027
14029 SIG_ADDTOOFFSET(+13),
14030 0x30, SIG_UINT16(0x0017), // bnt 0017
14031 0x38, SIG_MAGICDWORD, // pushi setLoop
14032 SIG_SELECTOR16(setLoop),
14033 0x7a, // push2
14034 0x7a, // push2
14035 0x78, // push1
14036 0x38, SIG_SELECTOR16(setCycle), // pushi setCycle
14037 0x7a, // push2
14038 0x51, SIG_ADDTOOFFSET(+1), // class End
14039 0x36, // push
14040 0x7c, // pushSelf
14041 0x72, SIG_ADDTOOFFSET(+2), // lofsa igor
14042 0x4a, SIG_UINT16(0x0010), // send 10 [ igor setLoop: 2 1 setCycle: End self ]
14043 0x32, SIG_ADDTOOFFSET(+2), // jmp [ end of method ]
14044 0x35, 0x01, // ldi 01
14045 0x65, SIG_ADDTOOFFSET(+1), // aTop cycles
14046 0x32, SIG_ADDTOOFFSET(+2), // jmp [ end of method ]
14047 SIG_END
14048 };
14049
14050 static const uint16 qfg4HectapusDeathPatch[] = {
14051 0x30, PATCH_UINT16(0x002b), // bnt 002b
14052 PATCH_ADDTOOFFSET(+13),
14053 0x30, PATCH_UINT16(0x001b), // bnt 001b
14054 PATCH_ADDTOOFFSET(+17),
14055 0x38, PATCH_SELECTOR16(setScript), // pushi setScript
14056 0x78, // push1
14057 0x76, // push0
14058 0x4a, PATCH_UINT16(0x0016), // send 16 [ igor setLoop: 2 1 setCycle: End self setScript: 0 ]
14059 0x3a, // toss
14060 0x48, // ret
14061 0x78, // push1
14062 0x69, PATCH_GETORIGINALBYTE(+45), // sTop cycles
14063 PATCH_END
14064 };
14065
14066 // Floppy 1.0 locks up when Ad Avis captures you with a Necrotaur in room 552,
14067 // and possibly other rooms. sBlackOut:changeState has one too many states
14068 // and doesn't increment the state number and cue enough in all scenarios.
14069 //
14070 // We fix this by removing the empty state 3 as later floppy versions do.
14071 //
14072 // Applies to: English Floppy 1.0
14073 // Responsible method: sBlackOut:changeState
14074 // Fixes bug: #11001
14075 static const uint16 qfg4AdAvisCaptureSignature[] = {
14076 0x31, 0x05, // bnt 05 [ state 3 ]
14077 SIG_ADDTOOFFSET(+6),
14078 0x35, SIG_MAGICDWORD, 0x03, // ldi 03
14079 0x1a, // eq?
14080 0x31, 0x05, // bnt 05 [ state 4 ]
14081 SIG_ADDTOOFFSET(+6),
14082 0x35, 0x04, // ldi 04
14083 SIG_ADDTOOFFSET(+83),
14084 0x35, 0x05, // ldi 05
14085 SIG_END
14086 };
14087
14088 static const uint16 qfg4AdAvisCapturePatch[] = {
14089 0x31, 0x10, // bnt 10 [ new state 3 ]
14090 PATCH_ADDTOOFFSET(+17),
14091 0x35, 0x03, // ldi 03 [ state 4 is now state 3 ]
14092 PATCH_ADDTOOFFSET(+83),
14093 0x35, 0x04, // ldi 04 [ state 5 is now state 4 ]
14094 PATCH_END
14095 };
14096
14097 // The character selection screen in room 140 can select the wrong character.
14098 // The showOff script brings each through their door and sets showOff:register
14099 // as they animate so that myChar:doVerb knows which animating character was
14100 // clicked. showOff:register is supposed to be 1 - 3 but showOff sets the wrong
14101 // fighter value and sets the rest at the wrong times.
14102 //
14103 // showOff state 0 sets register to 30 instead of 1 and so clicking the fighter
14104 // door during the first four seconds doesn't select any character. Instead it
14105 // proceeds to the skill screen without updating the character type global.
14106 // This global's initial value is zero, which happens to be the fighter value,
14107 // and so this appears to work unless a different character was first selected
14108 // and cancel was clicked. The magic user and thief register values are correct
14109 // but they're set one state too late, creating 2 - 4 second windows where
14110 // clicking their doors selects the previously animated character.
14111 //
14112 // We fix this by setting showOff:register to the correct fighter value and
14113 // setting the magic user and thief values one state earlier.
14114 //
14115 // Applies to: All versions
14116 // Responsible method: showOff:changeState
14117 // Fixes bug: #11002
14118 static const uint16 qfg4CharacterSelectSignature[] = {
14119 // state 0
14120 0x35, 0x1e, // ldi 1e
14121 0x65, SIG_ADDTOOFFSET(+1), // aTop register
14122 SIG_ADDTOOFFSET(+461),
14123 // state 5
14124 SIG_MAGICDWORD,
14125 0x32, SIG_UINT16(0x031e), // jmp 031e [ end of method ]
14126 0x3c, // dup
14127 SIG_ADDTOOFFSET(+219),
14128 // state 9
14129 0x35, 0x02, // ldi 02
14130 0x65, SIG_ADDTOOFFSET(+1), // aTop seconds
14131 0x32, SIG_UINT16(0x023b), // jmp 023b [ end of method ]
14132 SIG_END
14133 };
14134
14135 static const uint16 qfg4CharacterSelectPatch[] = {
14136 // state 0
14137 0x35, 0x01, // ldi 01
14138 PATCH_ADDTOOFFSET(+463),
14139 // state 5
14140 0x7a, // push2
14141 0x69, PATCH_GETORIGINALBYTE(+3), // sTop register
14142 PATCH_ADDTOOFFSET(+220),
14143 // state 9
14144 0x7a, // push2
14145 0x69, PATCH_GETORIGINALBYTE(+691), // sTop seconds
14146 0x39, 0x03, // pushi 03
14147 0x69, PATCH_GETORIGINALBYTE(+3), // sTop register
14148 PATCH_END
14149 };
14150
14151 // Clicking Look in the dungeon (room 670) responds with the dungeon description
14152 // followed by the generic message for not seeing anything. rm670:doVerb is
14153 // missing a return statement and so it proceeds with generic verb handling.
14154 //
14155 // Applies to: All versions
14156 // Responsible method: rm670:doVerb
14157 static const uint16 qfg4LookDungeonSignature[] = {
14158 0x38, SIG_SELECTOR16(say), // pushi say
14159 0x38, SIG_UINT16(0x0006), // pushi 0006
14160 SIG_MAGICDWORD,
14161 0x76, // push0
14162 0x78, // push1
14163 0x76, // push0
14164 0x76, // push0
14165 0x76, // push0
14166 0x38, SIG_UINT16(0x029e), // pushi 029e
14167 0x81, 0x5b, // lag 5b
14168 0x4a, SIG_UINT16(0x0010), // send 10 [ gloryMessager say: 0 1 0 0 0 670 ]
14169 SIG_END
14170 };
14171
14172 static const uint16 qfg4LookDungeonPatch[] = {
14173 PATCH_ADDTOOFFSET(+11),
14174 0x89, 0x0b, // lsg 0b [ room number, saves a byte ]
14175 0x81, 0x5b, // lag 5b
14176 0x4a, PATCH_UINT16(0x0010), // send 10 [ gloryMessager say: 0 1 0 0 0 670 ]
14177 0x48, // ret
14178 PATCH_END
14179 };
14180
14181 // When approaching the door to the great hall in staircase room 627 at night,
14182 // the message "You hear voices..." continues to occur even after witnessing
14183 // the argument between Katrina and Ad Avis in the floppy version. This is due
14184 // to not testing flag 112, which is set by the argument scene, and was fixed
14185 // in the CD version. We add the missing flag test.
14186 //
14187 // This incomplete logic to determine if Katrina and Ad Avis are in the great
14188 // hall is duplicated throughout this script. Although Sierra fixed this
14189 // instance in the CD version, it's the only one they fixed, while adding more
14190 // that lack the flag test. We fix those bugs in subsequent patches.
14191 //
14192 // Applies to: English Floppy, German Floppy
14193 // Responsible method: sDisplay:changeState(0)
14194 // Fixes bug: #10799
14195 static const uint16 qfg4ArgumentMessageFloppySignature[] = {
14196 SIG_MAGICDWORD,
14197 0x83, 0x02, // lal 02 [ message already said? ]
14198 0x30, SIG_UINT16(0x0013), // bnt 0013 [ say message ]
14199 0x38, SIG_SELECTOR16(handsOn), // pushi handsOn
14200 0x76, // push0
14201 0x81, 0x01, // lag 01
14202 0x4a, SIG_UINT16(0x0004), // send 04 [ Glory handsOn: ]
14203 0x38, SIG_SELECTOR16(dispose), // pushi dispose
14204 0x76, // push0
14205 0x54, SIG_UINT16(0x0004), // self 04
14206 0x32, SIG_UINT16(0x0079), // jmp 0079 [ end of method ]
14207 SIG_ADDTOOFFSET(+38),
14208 0x38, SIG_SELECTOR16(dispose), // pushi dispose
14209 0x76, // push0
14210 0x54, SIG_UINT16(0x0004), // self 04 [ self dispose: ]
14211 0x32, SIG_UINT16(0x0049), // jmp 0049 [ end of method ]
14212 SIG_END
14213 };
14214
14215 static const uint16 qfg4ArgumentMessageFloppyPatch[] = {
14216 PATCH_ADDTOOFFSET(+2),
14217 0x2f, 0x09, // bt 09 [ skip message if already said ]
14218 0x78, // push1
14219 0x39, 0x70, // pushi 70 [ flag 112 ]
14220 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ has argument occurred? ]
14221 0x31, 0x0b, // bnt 0b [ say message ]
14222 0x38, PATCH_SELECTOR16(handsOn), // pushi handsOn
14223 0x76, // push0
14224 0x81, 0x01, // lag 01
14225 0x4a, PATCH_UINT16(0x0004), // send 04 [ Glory handsOn: ]
14226 0x33, 0x26, // jmp 26 [ self dispose:, end of method ]
14227 PATCH_END
14228 };
14229
14230 // The great hall door options in room 627 are incorrect at night after Katrina
14231 // and Ad Avis argue. rm620Code:init is missing a flag test to prevent the
14232 // argument options from reoccurring. We add the missing flag test.
14233 //
14234 // Applies to: All versions
14235 // Responsible methods: rm620Code:init
14236 // Fixes bug: #10799
14237 static const uint16 qfg4Room627DoorOptionsSignature[] = {
14238 0x89, 0x0b, // lsg 0b [ room number ]
14239 SIG_ADDTOOFFSET(+28),
14240 0x39, 0x05, // pushi 05 [ argument door options ]
14241 0x72, SIG_ADDTOOFFSET(+2), // lofsa doorTopTeller
14242 0x4a, SIG_UINT16(0x000e), // send 0e [ doorTopTeller init: pUpperDoor 620 8 155 5 ]
14243 SIG_MAGICDWORD,
14244 0x33, 0x19, // jmp 19
14245 0x38, SIG_SELECTOR16(init), // pushi init
14246 0x38, SIG_UINT16(0x0005), // pushi 0005
14247 0x72, SIG_ADDTOOFFSET(+2), // lofsa pUpperDoor
14248 0x36, // push
14249 0x38, SIG_UINT16(0x026c), // pushi 026c
14250 0x39, 0x08, // pushi 08
14251 0x38, SIG_UINT16(0x009b), // pushi 009b
14252 0x78, // push1 [ normal door options ]
14253 0x72, SIG_ADDTOOFFSET(+2), // lofsa doorTopTeller
14254 0x4a, SIG_UINT16(0x000e), // send 0e [ doorTopTeller init: pUpperDoor 620 8 155 1 ]
14255 SIG_END
14256 };
14257
14258 static const uint16 qfg4Room627DoorOptionsPatch[] = {
14259 0x33, 0x0a, // jmp 0a
14260 PATCH_ADDTOOFFSET(+28),
14261 0x89, 0x0b, // lsg 0b [ room number ]
14262 0x34, PATCH_UINT16(0x0273), // ldi 627d
14263 0x1a, // eq?
14264 0x31, 0x14, // bnt 14 [ normal door options ]
14265 0x81, 0x79, // lag 79 [ night ]
14266 0x31, 0x10, // bnt 10 [ normal door options ]
14267 0x78, // push1
14268 0x39, 0x70, // pushi 70 [ flag 112 ]
14269 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ has argument occurred? ]
14270 0x2f, 0x07, // bt 07 [ normal door options ]
14271 0x39, 0x05, // pushi 05
14272 0x33, 0x04, // jmp 04 [ argument door options ]
14273 PATCH_END
14274 };
14275
14276 // The responses to the great hall door options in room 627 have problems. The
14277 // floppy version is missing message handlers for Open Door and Knock on Door
14278 // during the argument and so the door doesn't open. The CD version displays
14279 // the wrong Knock on Door message. All versions fail to test flag 112 to see
14280 // if the argument has already occurred.
14281 //
14282 // We fix all of this with a two part patch. First, sListened:register now
14283 // controls which message is displayed before the door opens. This allows
14284 // doorTopTeller:sayMessage to specify the correct message before running it.
14285 // To add flag tests, new message handlers, and set sListened:register, we take
14286 // advantage of identical message handlers in doorTopTeller:sayMessage. These
14287 // provide plenty of room for new code that then jumps into another handler to
14288 // continue and complete the work.
14289 //
14290 // Applies to: All versions
14291 // Responsible methods: sListened:changeState(0), doorTopTeller:sayMessage
14292 // Fixes bug: #10799
14293 static const uint16 qfg4Room627DoorResponsesSignature1[] = {
14294 0x65, SIG_ADDTOOFFSET(+17), // aTop state
14295 0x38, SIG_SELECTOR16(say), // pushi say
14296 0x38, SIG_UINT16(0x0006), // pushi 0006
14297 0x39, 0x08, // pushi 08
14298 0x38, SIG_UINT16(0x009b), // pushi 009b
14299 0x39, SIG_MAGICDWORD, 0x09, // pushi 09
14300 0x78, // push1
14301 0x7c, // pushSelf
14302 0x38, SIG_UINT16(0x026c), // pushi 026c
14303 0x81, 0x5b, // lag 5b
14304 0x4a, SIG_UINT16(0x0010), // send 10 [ gloryMessager say: 8 155 9 1 self 620 ]
14305 0x32, // jmp ... [ end of method ]
14306 SIG_END
14307 };
14308
14309 static const uint16 qfg4Room627DoorResponsesPatch1[] = {
14310 PATCH_ADDTOOFFSET(+21),
14311 0x39, 0x06, // pushi 06
14312 0x39, 0x08, // pushi 08
14313 0x38, PATCH_UINT16(0x009b), // pushi 009b
14314 0x39, 0x09, // pushi 09
14315 0x63, PATCH_GETORIGINALBYTEADJUST(+1, 0x10), // pToa register
14316 0x02, // add
14317 0x36, // push
14318 0x78, // push1
14319 0x7c, // pushSelf
14320 0x38, PATCH_UINT16(0x026c), // pushi 026c
14321 0x81, 0x5b, // lag 5b
14322 0x4a, PATCH_UINT16(0x0010), // send 10 [ gloryMessager say: 8 155 (9 + register) 1 self 620 ]
14323 PATCH_END
14324 };
14325
14326 static const uint16 qfg4Room627DoorResponsesFloppySignature2[] = {
14327 // Pick Lock (no argument) - missing flag check
14328 SIG_MAGICDWORD,
14329 0x30, SIG_UINT16(0x0072), // bnt 0072 [ next message handler ]
14330 0x81, 0x79, // lag 79 [ night ]
14331 0x30, SIG_UINT16(0x0028), // bnt 0028
14332 0x89, 0x0b, // lsg 0b [ room number ]
14333 0x34, SIG_UINT16(0x0273), // ldi 627d
14334 0x1a, // eq?
14335 0x30, SIG_UINT16(0x001f), // bnt 001f
14336 0x38, SIG_UINT16(0x00fe), // pushi clean [ hard-coded for floppy ]
14337 0x76, // push0
14338 0x54, SIG_UINT16(0x0004), // self 04
14339 0x78, // push1
14340 0x39, 0x71, // pushi 71
14341 0x45, 0x02, SIG_UINT16(0x0002), // callb proc0_2 02
14342 0x38, SIG_SELECTOR16(setScript), // pushi setScript
14343 0x78, // push1
14344 0x72, SIG_UINT16(0x0082), // lofsa sListened
14345 0x36, // push
14346 0x72, SIG_UINT16(0x01ae), // lofsa pUpperDoor
14347 0x4a, SIG_UINT16(0x0006), // send 06 [ pUpperDoor setScript: sListened ]
14348 0x32, SIG_UINT16(0x0131), // jmp 0131
14349 0x38, SIG_UINT16(0x0338), // pushi trySkill [ hard-coded for floppy ]
14350 0x7a, // push2
14351 0x39, 0x09, // pushi 09
14352 0x88, SIG_UINT16(0x01a6), // lsg 01a6
14353 0x81, 0x00, // lag 00
14354 0x4a, SIG_UINT16(0x0008), // send 08 [ hero trySkill: 9 global422 ]
14355 SIG_ADDTOOFFSET(+0xe4),
14356 // Open Door (no argument) - missing flag check
14357 0x78, // push1
14358 0x39, 0x71, // pushi 71
14359 0x45, 0x02, SIG_UINT16(0x0002), // callb proc0_2 02
14360 0x38, SIG_UINT16(0x00fe), // pushi clean [ hard-coded for floppy ]
14361 0x76, // push0
14362 0x54, SIG_UINT16(0x0004), // self 04
14363 SIG_END
14364 };
14365
14366 static const uint16 qfg4Room627DoorResponsesFloppyPatch2[] = {
14367 // Pick Lock (no argument) - missing flag check
14368 0x30, PATCH_UINT16(0x001a), // bnt 001a [ next message handler ]
14369 PATCH_ADDTOOFFSET(+2),
14370 0x30, PATCH_UINT16(0x00a1), // bnt 00a1 [ normal pick lock code in duplicate handler ]
14371 PATCH_ADDTOOFFSET(+6),
14372 0x30, PATCH_UINT16(0x0098), // bnt 0098 [ normal pick lock code in duplicate handler ]
14373 0x78, // push1
14374 0x39, 0x70, // pushi 70 [ flag 112 ]
14375 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ has argument occurred? ]
14376 0x2e, PATCH_UINT16(0x008e), // bt 008e [ normal pick lock code in duplicate handler ]
14377 0x33, 0x6d, // jmp 6d [ continue argument code in duplicate handler ]
14378 // Open Door (argument) - missing from floppy
14379 0x3c, // dup
14380 0x35, 0x0a, // ldi 0a
14381 0x1a, // eq?
14382 0x31, 0x07, // bnt 07 [ next message handler ]
14383 0x38, PATCH_SELECTOR16(register), // pushi register
14384 0x78, // push1
14385 0x78, // push1 [ "Ignoring the voices, you fling the door open..." ]
14386 0x33, 0x0b, // jmp 0b
14387 // Knock on Door (argument) - missing from floppy
14388 0x3c, // dup
14389 0x35, 0x0b, // ldi 0b
14390 0x1a, // eq?
14391 0x31, 0x45, // bnt 45 [ next message handler ]
14392 0x38, PATCH_SELECTOR16(register), // pushi register
14393 0x78, // push1
14394 0x7a, // push2 [ "Very polite of you. The door opens to your knock..." ]
14395 0x72, PATCH_UINT16(0x0082), // lofsa sListened
14396 0x4a, PATCH_UINT16(0x0006), // send 0006 [ sListened register: 1 or 2 ]
14397 0x32, PATCH_UINT16(0x004c), // jmp 004c [ continue argument code in duplicate handler ]
14398 PATCH_ADDTOOFFSET(+0xe4),
14399 // Open Door (no argument) - missing flag check
14400 0x78, // push1
14401 0x39, 0x70, // pushi 70 [ flag 112 ]
14402 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ has argument occurred? ]
14403 0x2f, 0x16, // bt 16 [ skip argument code if argument occurred ]
14404 0x32, PATCH_UINT16(0xff5c), // jmp ff5c [ continue argument code in duplicate handler ]
14405 PATCH_END
14406 };
14407
14408 static const uint16 qfg4Room627DoorResponsesCDSignature2[] = {
14409 // Pick Lock (no argument) - missing flag check
14410 0x38, SIG_UINT16(0x0101), // pushi clean [ hard-coded for CD ]
14411 0x76, // push0
14412 SIG_MAGICDWORD,
14413 0x54, SIG_UINT16(0x0004), // self 04
14414 0x78, // push1
14415 0x39, 0x71, // pushi 71
14416 0x45, 0x02, SIG_UINT16(0x0002), // callb proc0_2 02
14417 SIG_ADDTOOFFSET(+0x13b),
14418 // Open Door (no argument) - missing flag check
14419 0x78, // push1
14420 0x39, 0x71, // pushi 71
14421 0x45, 0x02, SIG_UINT16(0x0002), // callb proc0_2 02
14422 0x38, SIG_UINT16(0x0101), // pushi clean [ hard-coded for CD ]
14423 0x76, // push0
14424 0x54, SIG_UINT16(0x0004), // self 04
14425 SIG_ADDTOOFFSET(+0x7c),
14426 // Knock on Door (argument) - wrong message (CD regression)
14427 0x78, // push1
14428 0x39, 0x71, // pushi 71
14429 0x45, 0x02, SIG_UINT16(0x0002), // callb proc0_2 02
14430 0x38, SIG_UINT16(0x0101), // pushi clean [ hard-coded for CD ]
14431 0x76, // push0
14432 0x54, SIG_UINT16(0x0004), // self 04
14433 SIG_END
14434 };
14435
14436 static const uint16 qfg4Room627DoorResponsesCDPatch2[] = {
14437 // Pick Lock (no argument) - missing flag check
14438 0x78, // push1
14439 0x39, 0x70, // pushi 70 [ flag 112 ]
14440 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ has argument occurred? ]
14441 0x2f, 0x16, // bt 16 [ skip argument code if argument occurred ]
14442 0x32, PATCH_UINT16(0x0089), // jmp 0089 [ continue argument code in duplicate handler ]
14443 PATCH_ADDTOOFFSET(+0x13d),
14444 // Open Door (no argument) - missing flag check
14445 0x78, // push1
14446 0x39, 0x70, // pushi 70 [ flag 112 ]
14447 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ has argument occurred? ]
14448 0x2f, 0x16, // bt 16 [ skip argument code if argument occurred ]
14449 0x32, PATCH_UINT16(0xff40), // jmp ff40 [ continue argument code in duplicate handler ]
14450 PATCH_ADDTOOFFSET(+0x7e),
14451 // Knock on Door (argument) - wrong message (CD regression)
14452 0x38, PATCH_SELECTOR16(register), // pushi register
14453 0x78, // push1
14454 0x7a, // push2 [ "Very polite of you. The door opens to your knock..." ]
14455 0x72, PATCH_UINT16(0x00b6), // lofsa sListened
14456 0x4a, PATCH_UINT16(0x0006), // send 0006 [ sListened register: 2 ]
14457 0x32, PATCH_UINT16(0xfeb4), // jmp feb4 [ continue argument code in duplicate handler ]
14458 PATCH_END
14459 };
14460
14461 // When entering the great hall from room 627 while Katrina and Ad Avis argue,
14462 // room 627 says that the door squeaks even if it was oiled. The flag tests are
14463 // incorrect and out of sync with the logic in the great hall that plays the
14464 // squeak that kills ego. This logic had other bugs in the floppy version,
14465 // including setting an incorrect flag, and this patch fixes those too. The end
14466 // result is that the squeak message isn't displayed if the oiled flag is set.
14467 //
14468 // Applies to: All versions
14469 // Responsible methods: sListened:changeState(2), sListened2:changeState(2) (CD)
14470 // Fixes bug: #10799
14471 static const uint16 qfg4Room627SqueakFloppySignature[] = {
14472 0x89, 0x7d, // lsg 7d [ character type ]
14473 0x35, SIG_MAGICDWORD, 0x02, // ldi 02 [ thief ]
14474 0x1a, // eq? [ is thief? ]
14475 0x31, 0x08, // bnt 08 [ skip oil test if thief (incorrect, removed from CD) ]
14476 0x78, // push1
14477 0x39, 0x72, // pushi 72 [ flag 114 ]
14478 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 02 [ is door oiled? ]
14479 0x18, // not
14480 0x2f, 0x07, // bt 07 [ squeak message ]
14481 0x78, // push1
14482 0x39, 0x71, // pushi 71 [ flag 113 ]
14483 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 02 [ selected action other than listening? (incorrect) ]
14484 0x31, 0x33, // bnt 33 [ skip squeak message ]
14485 SIG_ADDTOOFFSET(+20),
14486 0x78, // push1
14487 0x39, 0x71, // pushi 71 [ flag 113 ]
14488 0x45, 0x02, SIG_UINT16(0x0002), // callb proc0_2 02 [ set flag 113 (incorrect, removed from CD) ]
14489 SIG_END
14490 };
14491
14492 static const uint16 qfg4Room627SqueakFloppyPatch[] = {
14493 0x33, 0x05, // jmp 05 [ skip thief test, always test oil flag ]
14494 PATCH_ADDTOOFFSET(+15),
14495 0x32, PATCH_UINT16(0x0039), // jmp 0039 [ skip squeak message if door has been oiled ]
14496 PATCH_ADDTOOFFSET(+26),
14497 0x32, PATCH_UINT16(0x0004), // jmp 0004 [ don't set flag 113 ]
14498 PATCH_END
14499 };
14500
14501 static const uint16 qfg4Room627SqueakCDSignature[] = {
14502 0x78, // push1
14503 0x39, 0x72, // pushi 72 [ flag 114 ]
14504 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 02 [ is door oiled? ]
14505 SIG_MAGICDWORD,
14506 0x18, // not
14507 0x2f, 0x07, // bt 07 [ squeak message ]
14508 0x78, // push1
14509 0x39, 0x71, // pushi 71 [ flag 113 ]
14510 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 02 [ selected action other than listening? (incorrect) ]
14511 0x31, 0x2c, // bnt 2c [ skip squeak message ]
14512 SIG_END
14513 };
14514
14515 static const uint16 qfg4Room627SqueakCDPatch[] = {
14516 PATCH_ADDTOOFFSET(+10),
14517 0x32, PATCH_UINT16(0x0032), // jmp 0032 [ skip squeak message if door has been oiled ]
14518 PATCH_END
14519 };
14520
14521 // Looking through the keyhole in room 627 into the great hall responds with a
14522 // generic message instead of the specific room description. sPeepingTom only
14523 // says the great hall message at night, which isn't relevant, and didn't work
14524 // since doorTeller wouldn't offer the keyhole option at night due to a missing
14525 // flag check which we fix. We restore the message by removing the night test.
14526 //
14527 // Applies to: All versions
14528 // Responsible method: sPeepingTom:changeState(1)
14529 // Fixes bug: #10799
14530 static const uint16 qfg4GreatHallKeyholeSignature[] = {
14531 SIG_MAGICDWORD,
14532 0x81, 0x79, // lag 79 [ night ]
14533 0x31, 0x1a, // bnt 1a [ skip keyhole message during day ]
14534 0x38, SIG_SELECTOR16(say), // pushi say
14535 SIG_END
14536 };
14537
14538 static const uint16 qfg4GreatHallKeyholePatch[] = {
14539 0x33, 0x02, // jmp 02 [ say keyhole message regardless of time ]
14540 PATCH_END
14541 };
14542
14543 // You can talk to the Burgomeister in his office when he's not there. Clicking
14544 // Talk on hero shows Burgomeister options even when alone because rm300:init
14545 // initializes heroTeller no matter who is in the room.
14546 //
14547 // We fix this by only initializing heroTeller when someone is in the room. The
14548 // Burgomeister appears when the time of day is 3 or less and Gypsy Davy
14549 // appears during room events 4, 5, and 6.
14550 //
14551 // Applies to: All versions
14552 // Responsible method: rm300:init
14553 // Fixes bug: #10754
14554 static const uint16 qfg4EmptyBurgoRoomSignature[] = {
14555 // start of heroTeller init: ...
14556 0x38, SIG_SELECTOR16(init), // pushi init
14557 SIG_MAGICDWORD,
14558 0x38, SIG_UINT16(0x0005), // pushi 0005
14559 0x89, 0x00, // lsg 00 [ hero ]
14560 0x38, SIG_UINT16(0x012c), // pushi 300d [ modNum ]
14561 0x39, 0x19, // pushi 25d [ noun ]
14562 0x38, SIG_UINT16(0x0080), // pushi 128d [ verb ]
14563 0x8b, 0x00, // lsl 00 [ event number ]
14564 0x3c, // dup
14565 0x35, 0x01, // ldi 01
14566 0x1a, // eq?
14567 0x31, 0x05, // bnt 05
14568 0x35, 0x10, // ldi 10 [ cond ]
14569 0x32, SIG_UINT16(0x0048), // jmp 0048
14570 0x3c, // dup
14571 0x35, 0x02, // ldi 02
14572 0x1a, // eq?
14573 0x31, 0x04, // bnt 04
14574 0x35, 0x11, // ldi 11 [ cond ]
14575 0x33, 0x3e, // jmp 3e
14576 0x3c, // dup
14577 0x35, 0x04, // ldi 04
14578 0x1a, // eq?
14579 0x31, 0x04, // bnt 04
14580 0x35, 0x13, // ldi 13 [ cond ]
14581 0x33, 0x34, // jmp 34
14582 0x3c, // dup
14583 0x35, 0x05, // ldi 05
14584 SIG_END
14585 };
14586
14587 static const uint16 qfg4EmptyBurgoRoomPatch[] = {
14588 0x89, 0x7b, // lsg 7b
14589 0x35, 0x03, // ldi 03
14590 0x24, // le? [ time of day <= 3 ]
14591 0x2f, 0x0e, // bt 0e [ burgomeister is here, call heroTeller:init ]
14592 0x8b, 0x00, // lsl 00
14593 0x35, 0x04, // ldi 04
14594 0x22, // lt? [ event number < 4 ]
14595 0x2f, 0x5f, // bt 5f [ no gypsy, skip heroTeller:init ]
14596 0x8b, 0x00, // lsl 00
14597 0x35, 0x06, // ldi 06
14598 0x1e, // gt? [ event number > 6 ]
14599 0x2f, 0x58, // bt 58 [ no gypsy, skip heroTeller:init ]
14600 // start of heroTeller init: ...
14601 0x38, PATCH_SELECTOR16(init), // pushi init
14602 0x39, 0x05, // pushi 05
14603 0x89, 0x00, // lsg 00 [ hero ]
14604 0x89, 0x0b, // lsg 0b [ modNum ]
14605 0x39, 0x19, // pushi 25d [ noun ]
14606 0x38, PATCH_UINT16(0x0080), // pushi 128d [ verb ]
14607 0x83, 0x00, // lal 00 [ event number ]
14608 0x36, // push
14609 0x31, 0x13, // bnt 13 [ event number == 0, next condition ]
14610 0x3c, // dup
14611 0x35, 0x05, // ldi 05
14612 0x1e, // gt?
14613 0x2f, 0x0d, // bt 0d [ event number > 5, next condition ]
14614 0x3c, // dup
14615 0x35, 0x0f, // ldi 0f
14616 0x02, // add [ cond = event number + 15d ]
14617 0x33, 0x31, // jmp 31
14618 PATCH_END
14619 };
14620
14621 // Ad Avis and his necrotaurs chase and catch hero to take him to the dungeon,
14622 // but this one-time event can repeat along with the entire dungeon sequence.
14623 //
14624 // The chase code is complex and spread across many scripts with many flags.
14625 // There is no code that resets all the flags upon capture and no code that
14626 // correctly tests if capture has occurred. This results in many ways to leave
14627 // at least one flag set and repeat the chase. What these code paths all have
14628 // in common is that the player has to walk through the town gate, room 290.
14629 //
14630 // We fix this by adding code to the start of rm290:init that clears all of the
14631 // relevant chase flags if capture has already occurred. Fortunately, this
14632 // method starts with unused debugging code that can be overwritten.
14633 //
14634 // Applies to: All versions
14635 // Responsible method: rm290:init
14636 // Fixes bug: #11056
14637 static const uint16 qfg4ChaseRepeatsSignature[] = {
14638 SIG_MAGICDWORD,
14639 0x81, 0xc9, // lag c9 [ debug mode ]
14640 0x31, 0x4e, // bnt 4e [ skip debug code ]
14641 0x78, // push1
14642 0x72, SIG_ADDTOOFFSET(+2), // lofsa prompt
14643 0x36, // push
14644 0x46, SIG_UINT16(0xfaff), // calle proc64255_1 02
14645 SIG_UINT16(0x0001),
14646 SIG_UINT16(0x0002),
14647 0xa3, 0x02, // sal 02
14648 0x39, 0x05, // pushi 05
14649 0x36, // push
14650 0x78, // push1
14651 0x7a, // push2
14652 0x39, 0x03, // pushi 03
14653 0x39, 0x04, // pushi 04
14654 0x46, SIG_UINT16(0xfde7), // calle proc64999_5 0a
14655 SIG_UINT16(0x0005),
14656 SIG_UINT16(0x000a),
14657 SIG_END
14658 };
14659
14660 static const uint16 qfg4ChaseRepeatsPatch[] = {
14661 0x78, // push1
14662 0x39, 0x6e, // pushi 6e [ flag 110 ]
14663 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ have you been captured? ]
14664 0x31, 0x49, // bnt 49 [ don't clear chase flags ]
14665 0x78, // push1
14666 0x39, 0x50, // pushi 50 [ flag 80 ]
14667 0x45, 0x03, PATCH_UINT16(0x0002), // callb proc0_3 02 [ clear chase flag 80 ]
14668 0x78, // push1
14669 0x39, 0x51, // pushi 51 [ flag 81 ]
14670 0x45, 0x03, PATCH_UINT16(0x0002), // callb proc0_3 02 [ clear chase flag 81 ]
14671 0x78, // push1
14672 0x38, PATCH_UINT16(0x00a4), // pushi 00a4 [ flag 164 ]
14673 0x45, 0x03, PATCH_UINT16(0x0002), // callb proc0_3 02 [ clear chase flag 164 ]
14674 0x33, 0x31, // jmp 31 [ continue room init ]
14675 PATCH_END
14676 };
14677
14678 // When the necrotaurs catch hero in the woods to take him to the dungeon, two
14679 // different script bugs randomly cause interpreter errors. This patch fixes
14680 // calling kUpdateScreenItem on a necrotaur with a deleted screen item.
14681 //
14682 // When hero is caught, the screen turns black before going to the dungeon.
14683 // There is an inconsistent delay and the necrotaurs will sometimes reappear
14684 // briefly on the black screen. These symptoms hint at the script's problems.
14685 // sBlackOut sets a 300 cycle delay but this rarely has an effect. Instead each
14686 // actor is hidden while their motions continue and sBlackOut advances when an
14687 // invisible necrotaur completes its JumpTo motion. JumpTo stores its client's
14688 // signal on initialization and unconditionally restores it upon completion. If
14689 // JumpTo completes after View:hide calls kDeleteScreenItem and sets the hidden
14690 // signal bit then signal is reverted and Actor:doit calls kUpdateScreenItem.
14691 //
14692 // We fix this by disposing of each cast member before painting black instead of
14693 // hiding them. This hides them and terminates their motions. This exposes the
14694 // previously unused 300 cycle delay, which is much longer than normal, so we
14695 // also change that to 4 seconds. This is consistent with existing behavior
14696 // and close to the delay used in room 290's working version of this script.
14697 // The majority of this patch is to free up the single byte needed to change
14698 // the 8-bit hide selector to 16-bit dispose.
14699 //
14700 // Several rooms that sBlackOut takes place in, such as 557, have doit methods
14701 // that can initiate new necrotaur motions after the cast has been disposed,
14702 // which also crashes. We fix this by clearing flag 35 as each of these rooms
14703 // requires it to be set to set a necrotaur motion. This is the "hunt" flag.
14704 // It's okay to clear it here as it gets cleared in the dungeon.
14705 //
14706 // Applies to: All versions
14707 // Responsible method: sBlackOut:changeState(3)
14708 // Fixes bug: #11056
14709 static const uint16 qfg4NecrotaurBlackoutSignature[] = {
14710 0x31, 0x4f, // bnt 4f [ next state ]
14711 SIG_ADDTOOFFSET(+11),
14712 SIG_MAGICDWORD,
14713 0x39, SIG_SELECTOR8(hide), // pushi hide
14714 0x81, 0x05, // lag 05
14715 0x4a, SIG_UINT16(0x0006), // send 06 [ cast eachElementDo: hide ]
14716 0x78, // push1 [ kUpdatePlane param count ]
14717 0x39, SIG_SELECTOR8(back), // pushi back
14718 0x78, // push1
14719 0x76, // push0
14720 0x39, SIG_ADDTOOFFSET(+1), // pushi picture
14721 0x78, // push1
14722 0x39, 0xff, // pushi ff
14723 0x38, SIG_ADDTOOFFSET(+2), // pushi yourself [ returns self ]
14724 0x76, // push0
14725 0x76, // push0 [ plane ]
14726 0x76, // push0
14727 0x81, 0x02, // lag 02
14728 0x4a, SIG_UINT16(0x0004), // send 04 [ room plane? ]
14729 0x4a, SIG_UINT16(0x0010), // send 10 [ room:plane back: 0 picture: -1 yourself: ]
14730 0x36, // push [ room:plane for kUpdatePlane ]
14731 SIG_ADDTOOFFSET(+29),
14732 0x34, SIG_UINT16(0x012c), // ldi 012c
14733 0x65, SIG_ADDTOOFFSET(+1), // aTop cycles [ cycles = 300 ]
14734 0x33, 0x1c, // jmp 1c [ end of method ]
14735 0x3c, // dup
14736 0x35, SIG_ADDTOOFFSET(+1), // ldi 04 or 05
14737 0x1a, // eq?
14738 0x31, 0x16, // bnt 16 [ end of method ]
14739 SIG_END
14740 };
14741
14742 static const uint16 qfg4NecrotaurBlackoutPatch[] = {
14743 0x31, 0x55, // bnt 55 [ next state ]
14744 PATCH_ADDTOOFFSET(+11),
14745 0x38, PATCH_SELECTOR16(dispose), // pushi dispose
14746 0x81, 0x05, // lag 05
14747 0x4a, PATCH_UINT16(0x0006), // send 06 [ cast eachElementDo: dispose ]
14748 0x78, // push1 [ kUpdatePlane param count ]
14749 0x76, // push0 [ plane ]
14750 0x76, // push0
14751 0x81, 0x02, // lag 02
14752 0x4a, PATCH_UINT16(0x0004), // send 04 [ room plane? ]
14753 0x36, // push [ room:plane for kUpdatePlane ]
14754 0x39, PATCH_SELECTOR8(back), // pushi back
14755 0x39, 0x01, // pushi 01
14756 0x39, 0x00, // pushi 00
14757 0x39, PATCH_GETORIGINALBYTE(+26), // pushi picture
14758 0x39, 0x01, // pushi 01
14759 0x39, 0xff, // pushi ff
14760 0x4a, PATCH_UINT16(0x000c), // send 0c [ room:plane back: 0 picture: -1 ]
14761 PATCH_ADDTOOFFSET(+29),
14762 0x35, 0x04, // ldi 04
14763 0x65, PATCH_GETORIGINALBYTEADJUST(+78, +2), // aTop seconds [ seconds = 4 ]
14764 0x78, // push1
14765 0x39, 0x23, // pushi 23
14766 0x45, 0x03, PATCH_UINT16(0x0002), // callb proc0_3 02 [ clear flag 35 ]
14767 0x3a, // toss
14768 0x48, // ret
14769 PATCH_END
14770 };
14771
14772 // When the necrotaurs catch hero in the woods to take him to the dungeon an
14773 // effectively random crash from the Grooper class can occur in the CD version.
14774 // See the inn door's script patch for details on these CD regressions.
14775 //
14776 // The error occurs when sBlackOut sets the motion of a necrotaur while it has
14777 // no cycler. In this case the cycler should still be Walk but CD regressions
14778 // can cause it to be cleared at unexpected times. We prevent this by setting
14779 // necrotaur cyclers to Walk when setting their final PChase motions.
14780 //
14781 // Applies to: PC CD
14782 // Responsible method: sBlackOut:changeState(0)
14783 // Fixes bug: #11056
14784 static const uint16 qfg4NecrotaurCaptureSignature[] = {
14785 SIG_MAGICDWORD,
14786 0x18, // not
14787 0x31, 0x38, // bnt 38
14788 0x38, SIG_ADDTOOFFSET(+2), // pushi distanceTo
14789 0x78, // push1
14790 0x72, SIG_ADDTOOFFSET(+2), // lofsa nec (nec1 or nec2 or nec3)
14791 0x36, // push
14792 0x81, 0x00, // lag 00
14793 0x4a, SIG_UINT16(0x0006), // send 06 [ hero distanceTo: nec ]
14794 0x36, // push
14795 0x35, 0x19, // ldi 19
14796 0x1e, // gt?
14797 0x31, 0x19, // bnt 19
14798 0x38, SIG_SELECTOR16(setMotion), // pushi setMotion
14799 0x38, SIG_UINT16(0x0004), // pushi 0004
14800 0x51, 0x6c, // class PChase
14801 0x36, // push
14802 0x89, 0x00, // lsg 00
14803 0x39, 0x19, // pushi 19
14804 0x72, SIG_ADDTOOFFSET(+2), // lofsa nec
14805 0x36, // push
14806 0x72, // lofsa nec
14807 SIG_END
14808 };
14809
14810 static const uint16 qfg4NecrotaurCapturePatch[] = {
14811 0x2f, 0x39, // bt 39
14812 0x38, PATCH_GETORIGINALUINT16(+4), // pushi distanceTo
14813 0x78, // push1
14814 0x74, PATCH_GETORIGINALUINT16(+8), // lofss nec (nec1 or nec2 or nec3)
14815 0x81, 0x00, // lag 00
14816 0x4a, PATCH_UINT16(0x0006), // send 06 [ hero distanceTo: nec ]
14817 0x39, 0x19, // pushi 19
14818 0x24, // le?
14819 0x31, 0x1c, // bnt 1c
14820 0x38, PATCH_SELECTOR16(setCycle), // pushi setCycle
14821 0x78, // push1
14822 0x51, 0x17, // class Walk
14823 0x36, // push
14824 0x38, PATCH_SELECTOR16(setMotion), // pushi setMotion
14825 0x39, 0x04, // pushi 04
14826 0x51, 0x6c, // class PChase
14827 0x36, // push
14828 0x89, 0x00, // lsg 00
14829 0x39, 0x19, // pushi 19
14830 0x72, PATCH_GETORIGINALUINT16(+8), // lofsa nec
14831 0x36, // push
14832 PATCH_END
14833 };
14834
14835 // When entering room 600 from the south at night, paladins receive a message
14836 // about the two necrotaurs guarding the gate even when they're not there. The
14837 // necrotaurs' appearance depends on flags, events, and if they've been killed,
14838 // but the message code only tests if it's night.
14839 //
14840 // We fix this by only showing the message if there are at least 4 cast members
14841 // in the room at night, which only occurs when the necrotaurs are present.
14842 //
14843 // Applies to: All versions
14844 // Responsible method: sFromSouth:changeState(1)
14845 // Fixes bug: #11057
14846 static const uint16 qfg4NecrotaurMessageSignature[] = {
14847 0x30, SIG_UINT16(0x0052), // bnt 0052 [ state 1 ]
14848 SIG_ADDTOOFFSET(+0x4f),
14849 SIG_MAGICDWORD,
14850 0x32, SIG_UINT16(0x004b), // jmp 004b [ end of method ]
14851 0x3c, // dup
14852 0x35, 0x01, // ldi 01
14853 0x1a, // eq?
14854 0x30, SIG_UINT16(0x0044), // bnt 0044 [ end of method ]
14855 0x81, 0x79, // lag 79 [ night ]
14856 0x30, SIG_UINT16(0x0022), // bnt 0022
14857 0x89, 0x7d, // lsg 7d [ character type ]
14858 0x35, 0x03, // ldi 03 [ paladin ]
14859 0x1a, // eq?
14860 0x30, SIG_UINT16(0x0011), // bnt 0011 [ skip message ]
14861 0x38, SIG_SELECTOR16(say), // pushi say
14862 0x38, SIG_UINT16(0x0003), // pushi 0003
14863 SIG_ADDTOOFFSET(+0x31),
14864 0x3a, // toss
14865 SIG_END
14866 };
14867
14868 static const uint16 qfg4NecrotaurMessagePatch[] = {
14869 0x3a, // toss
14870 0x31, 0x50, // bnt 50 [ state 1 ]
14871 PATCH_ADDTOOFFSET(+0x4f),
14872 0x48, // ret
14873 0x81, 0x79, // lag 79 [ night ]
14874 0x31, 0x2c, // bnt 2c
14875 0x7a, // push2
14876 0x81, 0x7d, // lag 7d [ character type ]
14877 0x22, // lt?
14878 0x31, 0x1d, // bnt 1d [ skip message ]
14879 0x39, 0x04, // pushi 04
14880 0x39, PATCH_SELECTOR8(size), // pushi size
14881 0x76, // push0
14882 0x81, 0x05, // lag 05
14883 0x4a, PATCH_UINT16(0x0004), // send 04 [ cast size? ]
14884 0x24, // le? [ at least 4 cast members? ]
14885 0x31, 0x10, // bnt 10 [ skip message ]
14886 0x38, PATCH_SELECTOR16(say), // pushi say
14887 0x39, 0x03, // pushi 03
14888 PATCH_ADDTOOFFSET(+0x31),
14889 0x48, // ret
14890 PATCH_END
14891 };
14892
14893 // When returning to the castle gate (room 600) from the dungeon (room 670) the
14894 // gate options and messages can be out of sync with the gatekeeper's presence.
14895 // Returning from the dungeon is room event 11, which causes rm600:init to
14896 // advance the time to night and hide the gatekeeper, but this happens after
14897 // rm600:init has initialized gateTeller based on whether it was day or night.
14898 //
14899 // We fix this by setting the night global before gateTeller is initialized
14900 // during room event 11. Fortunately, the gateTeller code is preceded by a
14901 // redundant condition which is always false and can be overwritten.
14902 //
14903 // Applies to: All versions
14904 // Responsible method: rm600:init
14905 // Fixes bug: #11044
14906 static const uint16 qfg4GateOptionsSignature[] = {
14907 0x35, 0x0a, // ldi 0a [ event 10 ]
14908 0x1a, // eq? [ always false, tested earlier ]
14909 0x31, SIG_ADDTOOFFSET(+1), // bnt [ gateTeller init ]
14910 0x38, SIG_SELECTOR16(posn), // pushi posn
14911 0x7a, // push2
14912 0x39, SIG_MAGICDWORD, 0xe2, // pushi e2
14913 0x39, 0x69, // pushi 69
14914 0x72, // lofsa aGate
14915 SIG_END
14916 };
14917
14918 static const uint16 qfg4GateOptionsPatch[] = {
14919 0x35, 0x0b, // ldi 0b [ event 11, came from dungeon ]
14920 PATCH_ADDTOOFFSET(+3),
14921 0x35, 0x01, // ldi 01
14922 0xa1, 0x79, // sag 79 [ night = 1 ]
14923 0x33, PATCH_GETORIGINALBYTEADJUST(+4, -6), // jmp [ gateTeller init ]
14924 PATCH_END
14925 };
14926
14927 // Six castle rooms contain a bug where oiling one door in the room effectively
14928 // oils the rest when picking locks. sPickLock doesn't test which door is being
14929 // opened, only their oil flags, and if any are set then it doesn't squeak.
14930 //
14931 // We fix this by adding code to test hero's position to determine which door is
14932 // being opened and then test the correct flag. Ideally we should only need two
14933 // versions of this patch: a two-door version and a three-door. Unfortunately,
14934 // each two-door sPickLock is different. Script 634 adds an unnecessary room
14935 // test, 643 and 644 transpose the left and right door flags, and script 661
14936 // tests a flag lower than 128 which changes a key instruction size. Room 631's
14937 // sPickLock is in script 634 and it is the only room that uses it.
14938 //
14939 // Applies to: All versions
14940 // Responsible method: sPickLock:changeState(1) in scripts 634, 640, 642, 643, 644, 661
14941 // Fixes bug: #10832
14942 static const uint16 qfg4Room631LockSqueakSignature[] = {
14943 0x89, 0x0b, // lsg 0b
14944 0x34, SIG_UINT16(0x0277), // ldi 0277
14945 0x1a, // eq? [ is room 631? (always true) ]
14946 0x31, SIG_MAGICDWORD, 0x14, // bnt 14 [ right flag test ]
14947 0x78, // push1
14948 0x38, SIG_UINT16(0x00d1), // pushi 00d1 [ flag 209 ]
14949 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 02 [ is left door oiled? ]
14950 0x31, 0x0a, // bnt 0a [ right flag test ]
14951 0x38, SIG_SELECTOR16(cue), // pushi cue
14952 0x76, // push0
14953 0x54, SIG_UINT16(0x0004), // self 04 [ self cue: ]
14954 SIG_ADDTOOFFSET(+21),
14955 0x38, SIG_SELECTOR16(cue), // pushi cue [ no squeak ]
14956 SIG_ADDTOOFFSET(+7),
14957 0x39, SIG_SELECTOR8(play), // pushi play [ squeak ]
14958 SIG_END
14959 };
14960
14961 static const uint16 qfg4Room631LockSqueakPatch[] = {
14962 0x38, PATCH_UINT16(0x00a0), // pushi 00a0
14963 0x78, // push1 [ x ]
14964 0x76, // push0
14965 0x81, 0x00, // lag 00
14966 0x4a, PATCH_UINT16(0x0004), // send 04 [ hero x? ]
14967 0x1e, // gt? [ 160 > hero:x ]
14968 0x31, 0x0f, // bnt 0f [ right flag test ]
14969 0x78, // push1
14970 0x38, PATCH_UINT16(0x00d1), // pushi 00d1 [ flag 209 ]
14971 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ is left door oiled? ]
14972 0x31, 0x21, // bnt 21 [ squeak ]
14973 0x33, 0x15, // jmp 15 [ no squeak ]
14974 PATCH_END
14975 };
14976
14977 // rooms 640 and 642 have three doors
14978 static const uint16 qfg4Room640LockSqueakSignature[] = {
14979 0x78, // push1
14980 0x38, SIG_ADDTOOFFSET(+2), // pushi middle flag
14981 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 02 [ is middle door oiled? ]
14982 SIG_MAGICDWORD,
14983 0x31, 0x0a, // bnt 0a [ left door flag test ]
14984 0x38, SIG_SELECTOR16(cue), // pushi cue
14985 0x76, // push0
14986 0x54, SIG_UINT16(0x0004), // self 04 [ self cue: ]
14987 0x32, SIG_ADDTOOFFSET(+2), // jmp [ end of method ]
14988 0x78, // push1
14989 0x38, SIG_ADDTOOFFSET(+2), // pushi left flag
14990 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 02 [ is left door oiled? ]
14991 0x31, 0x0a, // bnt 0a [ right door flag test ]
14992 0x38, SIG_SELECTOR16(cue), // pushi cue
14993 0x76, // push0
14994 0x54, SIG_UINT16(0x0004), // self 04 [ self cue: ]
14995 0x32, SIG_ADDTOOFFSET(+2), // jmp [ end of method ]
14996 0x78, // push1
14997 0x38, SIG_ADDTOOFFSET(+2), // pushi right flag
14998 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 02 [ is right door oiled? ]
14999 0x31, 0x0a, // bnt 0a [ squeak ]
15000 0x38, SIG_SELECTOR16(cue), // pushi cue
15001 0x76, // push0
15002 0x54, SIG_UINT16(0x0004), // self 04 [ self cue: ]
15003 SIG_ADDTOOFFSET(+3),
15004 0x39, SIG_SELECTOR8(play), // pushi play [ squeak ]
15005 SIG_ADDTOOFFSET(+28),
15006 0x38, SIG_SELECTOR16(cue), // pushi cue [ no squeak ]
15007 SIG_END
15008 };
15009
15010 static const uint16 qfg4Room640LockSqueakPatch[] = {
15011 0x39, 0x2e, // pushi 2e
15012 0x78, // push1 [ x ]
15013 0x76, // push0
15014 0x81, 0x00, // lag 00
15015 0x4a, PATCH_UINT16(0x0004), // send 04 [ hero x? ]
15016 0x1e, // gt? [ 46 > hero:x ]
15017 0x31, 0x0c, // bnt 0c [ middle door test ]
15018 0x78, // push1
15019 0x38, PATCH_GETORIGINALUINT16(+22), // pushi left flag
15020 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ is left door oiled? ]
15021 0x31, 0x26, // bnt 26 [ squeak ]
15022 0x33, 0x42, // jmp 42 [ no squeak ]
15023 0x60, // pprev [ hero:x ]
15024 0x34, PATCH_UINT16(0x00e6), // ldi 00e6
15025 0x22, // lt? [ hero:x < 230 ]
15026 0x31, 0x0c, // bnt 0c [ right flag test ]
15027 0x78, // push1
15028 0x38, PATCH_GETORIGINALUINT16(+2), // pushi middle flag
15029 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ is middle door oiled? ]
15030 0x31, 0x13, // bnt 13 [ squeak ]
15031 0x33, 0x2f, // jmp 2f [ no squeak ]
15032 0x78, // push1
15033 0x38, PATCH_GETORIGINALUINT16(+42), // pushi right flag
15034 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ is right door oiled? ]
15035 0x31, 0x07, // bnt 07 [ squeak ]
15036 0x33, 0x23, // jmp 23 [ no squeak ]
15037 PATCH_END
15038 };
15039
15040 // room 643 has two doors but no room test as in script 634
15041 static const uint16 qfg4Room643LockSqueakSignature[] = {
15042 0x78, // push1
15043 0x38, SIG_ADDTOOFFSET(+2), // pushi flag
15044 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 02 [ is door oiled? ]
15045 SIG_MAGICDWORD,
15046 0x31, 0x0a, // bnt 0a [ other door flag test ]
15047 0x38, SIG_SELECTOR16(cue), // pushi cue
15048 0x76, // push0
15049 0x54, SIG_UINT16(0x0004), // self 04 [ self cue: ]
15050 0x32, SIG_ADDTOOFFSET(+2), // jmp [ end of method ]
15051 0x78, // push1
15052 0x38, SIG_ADDTOOFFSET(+2), // pushi flag
15053 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 02 [ is other door oiled? ]
15054 0x31, 0x0a, // bnt 0a [ squeak ]
15055 0x38, SIG_SELECTOR16(cue), // pushi cue
15056 0x76, // push0
15057 0x54, SIG_UINT16(0x0004), // self 04 [ self cue: ]
15058 SIG_ADDTOOFFSET(+3),
15059 0x39, SIG_SELECTOR8(play), // pushi play [ squeak ]
15060 SIG_ADDTOOFFSET(+28),
15061 0x38, SIG_SELECTOR16(cue), // pushi cue [ no squeak ]
15062 SIG_END
15063 };
15064
15065 static const uint16 qfg4Room643LockSqueakPatch[] = {
15066 0x38, PATCH_UINT16(0x00a0), // pushi 00a0
15067 0x78, // push1 [ x ]
15068 0x76, // push0
15069 0x81, 0x00, // lag 00
15070 0x4a, PATCH_UINT16(0x0004), // send 04 [ hero x? ]
15071 0x1e, // gt? [ 160 > hero:x ]
15072 0x31, 0x0c, // bnt 0c [ right flag test ]
15073 0x78, // push1
15074 0x38, PATCH_GETORIGINALUINT16(+22), // pushi left flag
15075 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ is left door oiled? ]
15076 0x31, 0x11, // bnt 11 [ squeak ]
15077 0x33, 0x2d, // jmp 2d [ no squeak ]
15078 0x78, // push1
15079 0x38, PATCH_GETORIGINALUINT16(+2), // pushi right flag
15080 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ is right door oiled? ]
15081 0x31, 0x05, // bnt 05 [ squeak ]
15082 0x33, 0x21, // jmp 21 [ no squeak ]
15083 PATCH_END
15084 };
15085
15086 // room 644 is the same as 643 except the left and right door flags
15087 // were transposed, so at least the 643 signature can be reused
15088 static const uint16 qfg4Room644LockSqueakPatch[] = {
15089 0x38, PATCH_UINT16(0x00a0), // pushi 00a0
15090 0x78, // push1 [ x ]
15091 0x76, // push0
15092 0x81, 0x00, // lag 00
15093 0x4a, PATCH_UINT16(0x0004), // send 04 [ hero x? ]
15094 0x1e, // gt? [ 160 > hero:x ]
15095 0x31, 0x0c, // bnt 0c [ right flag test ]
15096 0x78, // push1
15097 0x38, PATCH_GETORIGINALUINT16(+2), // pushi left flag
15098 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ is left door oiled? ]
15099 0x31, 0x11, // bnt 11 [ squeak ]
15100 0x33, 0x2d, // jmp 2d [ no squeak ]
15101 0x78, // push1
15102 0x38, PATCH_GETORIGINALUINT16(+22), // pushi right flag
15103 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ is right door oiled? ]
15104 0x31, 0x05, // bnt 05 [ squeak ]
15105 0x33, 0x21, // jmp 21 [ no squeak ]
15106 PATCH_END
15107 };
15108
15109 // room 661 is the same as 644 but has a different instruction size in the middle
15110 static const uint16 qfg4Room661LockSqueakSignature[] = {
15111 0x78, // push1
15112 0x38, SIG_UINT16(0x00e0), // pushi 00e0 [ left flag ]
15113 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 02 [ is left door oiled? ]
15114 SIG_MAGICDWORD,
15115 0x31, 0x0a, // bnt 0a [ other door flag test ]
15116 0x38, SIG_SELECTOR16(cue), // pushi cue
15117 0x76, // push0
15118 0x54, SIG_UINT16(0x0004), // self 04 [ self cue: ]
15119 0x32, SIG_ADDTOOFFSET(+2), // jmp [ end of method ]
15120 0x78, // push1
15121 0x39, 0x76, // pushi 76 [ right flag ]
15122 0x45, 0x04, SIG_UINT16(0x0002), // callb proc0_4 02 [ is right door oiled? ]
15123 0x31, 0x0a, // bnt 0a [ squeak ]
15124 0x38, SIG_SELECTOR16(cue), // pushi cue
15125 0x76, // push0
15126 0x54, SIG_UINT16(0x0004), // self 04 [ self cue: ]
15127 SIG_ADDTOOFFSET(+3),
15128 0x39, SIG_SELECTOR8(play), // pushi play [ squeak ]
15129 SIG_ADDTOOFFSET(+28),
15130 0x38, SIG_SELECTOR16(cue), // pushi cue [ no squeak ]
15131 SIG_END
15132 };
15133
15134 static const uint16 qfg4Room661LockSqueakPatch[] = {
15135 0x38, PATCH_UINT16(0x00a0), // pushi 00a0
15136 0x78, // push1 [ x ]
15137 0x76, // push0
15138 0x81, 0x00, // lag 00
15139 0x4a, PATCH_UINT16(0x0004), // send 04 [ hero x? ]
15140 0x1e, // gt? [ 160 > hero:x ]
15141 0x31, 0x0c, // bnt 0c [ right flag test ]
15142 0x78, // push1
15143 0x38, PATCH_UINT16(0x00e0), // pushi 00e0 [ left flag ]
15144 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ is left door oiled? ]
15145 0x31, 0x10, // bnt 10 [ squeak ]
15146 0x33, 0x2c, // jmp 2c [ no squeak ]
15147 0x78, // push1
15148 0x39, 0x76, // pushi 76 [ right flag ]
15149 0x45, 0x04, PATCH_UINT16(0x0002), // callb proc0_4 02 [ is right door oiled? ]
15150 0x31, 0x05, // bnt 05 [ squeak ]
15151 0x33, 0x21, // jmp 21 [ no squeak ]
15152 PATCH_END
15153 };
15154
15155 // When exiting the Thieves' Guild secret passage (room 340) to the town bridge
15156 // (room 290), hero appears in an out of bounds area on the far right of the
15157 // screen for 120 ticks and then abruptly teleports to the bridge secret exit.
15158 // This is due to not initializing hero until after the 120 tick delay, causing
15159 // him to be initially visible in his position from the previous room.
15160 //
15161 // We fix this by hiding hero initially so that he appears when emerging from
15162 // the secret exit under the bridge after the delay.
15163 //
15164 // Applies to: All versions
15165 // Responsible method: sThiefEnter:changeState
15166 // Fixes bug: #10774
15167 static const uint16 qfg4BridgeSecretExitSignature[] = {
15168 0x3c, // dup
15169 0x35, 0x00, // ldi 00
15170 0x1a, // eq?
15171 SIG_MAGICDWORD,
15172 0x31, 0x07, // bnt 07 [ state 1 ]
15173 0x35, 0x78, // ldi 78
15174 0x65, SIG_ADDTOOFFSET(+1), // aTop ticks [ ticks = 120 ]
15175 0x32, SIG_UINT16(0x00dd), // jmp 00dd [ end of method ]
15176 0x3c, // dup
15177 0x35, 0x01, // ldi 01
15178 0x1a, // eq?
15179 0x30, SIG_UINT16(0x0020), // bnt 0020 [ state 2 ]
15180 SIG_END
15181 };
15182
15183 static const uint16 qfg4BridgeSecretExitPatch[] = {
15184 0x2f, 0x0c, // bt 0c [ state 1 ]
15185 0x39, PATCH_SELECTOR8(hide), // pushi hide
15186 0x76, // push0
15187 0x81, 0x00, // lag 00
15188 0x4a, PATCH_UINT16(0x0004), // send 04 [ hero hide: ]
15189 0x35, 0x78, // ldi 78
15190 0x65, PATCH_GETORIGINALBYTE(+9), // aTop ticks [ ticks = 120 ]
15191 0x3c, // dup
15192 0x35, 0x01, // ldi 01
15193 0x1a, // eq?
15194 0x31, 0x20, // bnt 20 [ state 2 ]
15195 PATCH_END
15196 };
15197
15198 // The bone cage in room 770 has two problems. Clicking Do and selecting Jump
15199 // Out of Cage as a thief leaves the cursor stuck. Selecting Break Cage as a
15200 // fighter allows the player to click during what's supposed to be a handsOff
15201 // script and break the game. Both bugs are due to egoTeller:sayMessage. Its
15202 // Jump handler is missing a call to self:clean and its Break Cage handler
15203 // incorrectly calls self:clean after running sBreakBones instead of before.
15204 //
15205 // We fix this by calling self:clean before running sBreakBones or sJumpOut.
15206 //
15207 // Applies to: All versions
15208 // Responsible method: egoTeller:sayMessage
15209 // Fixes bug: #11238
15210 static const uint16 qfg4BoneCageTellerSignature[] = {
15211 0x30, SIG_UINT16(0x001b), // bnt 001b
15212 0x38, SIG_SELECTOR16(setScript), // pushi setScript
15213 0x38, SIG_UINT16(0x0003), // pushi 0003
15214 0x72, SIG_ADDTOOFFSET(+2), // lofsa sBreakBones
15215 0x36, // push
15216 0x76, // push0
15217 0x76, // push0
15218 0x81, 0x02, // lag 02
15219 0x4a, SIG_UINT16(0x000a), // send 0a [ rm770 setScript: sBreakBones 0 0 ]
15220 0x38, SIG_ADDTOOFFSET(+2), // pushi clean
15221 0x76, // push0
15222 0x54, SIG_UINT16(0x0004), // self 04 [ self clean: ]
15223 0x32, SIG_UINT16(0x0021), // jmp 0021 [ end of method ]
15224 SIG_MAGICDWORD,
15225 0x3c, // dup
15226 0x35, 0x23, // ldi 23 [ "Jump Out of Cage" ]
15227 0x1a, // eq?
15228 0x30, SIG_UINT16(0x0010), // bnt 0010
15229 SIG_END
15230 };
15231
15232 static const uint16 qfg4BoneCageTellerPatch[] = {
15233 0x31, 0x15, // bnt 15
15234 0x38, PATCH_GETORIGINALUINT16(+21), // pushi clean
15235 0x76, // push0
15236 0x54, PATCH_UINT16(0x0004), // self 0004 [ self clean: ]
15237 0x38, PATCH_SELECTOR16(setScript), // pushi setScript
15238 0x78, // push1
15239 0x74, PATCH_GETORIGINALUINT16(+10), // lofss sBreakBones
15240 0x81, 0x02, // lag 02
15241 0x4a, PATCH_UINT16(0x0006), // send 06 [ rm770 setScript: sBreakBones ]
15242 0x3a, // toss
15243 0x48, // ret
15244 0x3c, // dup
15245 0x35, 0x23, // ldi 23 [ "Jump Out of Cage" ]
15246 0x1a, // eq?
15247 0x30, PATCH_UINT16(0x0017), // bnt 0017
15248 0x38, PATCH_GETORIGINALUINT16(+21), // pushi clean
15249 0x76, // push0
15250 0x54, PATCH_UINT16(0x0004), // self 04 [ self clean: ]
15251 PATCH_END
15252 };
15253
15254 // script, description, signature patch
15255 static const SciScriptPatcherEntry qfg4Signatures[] = {
15256 { true, 0, "prevent autosave from deleting save games", 1, qfg4AutosaveSignature, qfg4AutosavePatch },
15257 { true, 0, "fix inventory leaks across restarts", 1, qfg4RestartSignature, qfg4RestartPatch },
15258 { true, 1, "disable volume reset on startup", 1, sci2VolumeResetSignature, sci2VolumeResetPatch },
15259 { true, 1, "disable video benchmarking", 1, qfg4BenchmarkSignature, qfg4BenchmarkPatch },
15260 { true, 7, "fix consecutive moonrises", 1, qfg4MoonriseSignature, qfg4MoonrisePatch },
15261 { true, 10, "fix setLooper calls (2/2)", 2, qfg4SetLooperSignature2, qfg4SetLooperPatch2 },
15262 { true, 11, "fix spell effect disposal", 1, qfg4EffectDisposalSignature, qfg4EffectDisposalPatch },
15263 { true, 11, "fix trigger after summon staff (1/2)", 1, qfg4TriggerStaffSignature1, qfg4TriggerStaffPatch1 },
15264 { true, 11, "fix trigger after summon staff (2/2)", 1, qfg4TriggerStaffSignature2, qfg4TriggerStaffPatch2 },
15265 { true, 13, "fix spell effect disposal", 1, qfg4EffectDisposalSignature, qfg4EffectDisposalPatch },
15266 { true, 28, "fix lingering rations icon after eating", 1, qfg4LeftoversSignature, qfg4LeftoversPatch },
15267 { true, 31, "fix setScaler calls", 1, qfg4SetScalerSignature, qfg4SetScalerPatch },
15268 { true, 41, "fix conditional void calls", 3, qfg4ConditionalVoidSignature, qfg4ConditionalVoidPatch },
15269 { true, 50, "fix random revenant kopeks", 1, qfg4SearchRevenantSignature, qfg4SearchRevenantPatch },
15270 { true, 51, "Floppy: fix ad avis capture lockup", 1, qfg4AdAvisCaptureSignature, qfg4AdAvisCapturePatch },
15271 { true, 51, "fix necrotaur blackout", 1, qfg4NecrotaurBlackoutSignature,qfg4NecrotaurBlackoutPatch },
15272 { true, 51, "CD: fix necrotaur capture", 3, qfg4NecrotaurCaptureSignature, qfg4NecrotaurCapturePatch },
15273 { true, 53, "NRS: fix wraith lockup", 1, qfg4WraithLockupNrsSignature, qfg4WraithLockupNrsPatch },
15274 { true, 83, "fix incorrect array type", 1, qfg4TrapArrayTypeSignature, qfg4TrapArrayTypePatch },
15275 { true, 140, "fix character selection", 1, qfg4CharacterSelectSignature, qfg4CharacterSelectPatch },
15276 { true, 250, "fix hectapus death lockup", 1, qfg4HectapusDeathSignature, qfg4HectapusDeathPatch },
15277 { true, 260, "CD: fix inn door crash", 1, qfg4InnDoorCDSignature, qfg4InnDoorCDPatch },
15278 { true, 270, "fix town gate after a staff dream", 1, qfg4DreamGateSignature, qfg4DreamGatePatch },
15279 { true, 270, "fix town gate doormat at night", 1, qfg4TownGateDoormatSignature, qfg4TownGateDoormatPatch },
15280 { true, 290, "fix chase repeating", 1, qfg4ChaseRepeatsSignature, qfg4ChaseRepeatsPatch },
15281 { true, 290, "fix bridge secret exit", 1, qfg4BridgeSecretExitSignature, qfg4BridgeSecretExitPatch },
15282 { true, 300, "fix empty burgomeister room teller", 1, qfg4EmptyBurgoRoomSignature, qfg4EmptyBurgoRoomPatch },
15283 { true, 320, "fix pathfinding at the inn", 1, qfg4InnPathfindingSignature, qfg4InnPathfindingPatch },
15284 { true, 320, "fix talking to absent innkeeper", 1, qfg4AbsentInnkeeperSignature, qfg4AbsentInnkeeperPatch },
15285 { true, 320, "CD: fix domovoi never appearing", 1, qfg4DomovoiInnSignature, qfg4DomovoiInnPatch },
15286 { true, 324, "CD: fix domovoi never appearing", 1, qfg4DomovoiInnSignature, qfg4DomovoiInnPatch },
15287 { true, 340, "CD/Floppy: fix guild tunnel access (1/3)", 1, qfg4GuildWalkSignature1, qfg4GuildWalkPatch1 },
15288 { true, 340, "CD/Floppy: fix guild tunnel access (2/3)", 1, qfg4GuildWalkSignature2, qfg4GuildWalkPatch2 },
15289 { false, 340, "CD: fix guild tunnel access (3/3)", 1, qfg4GuildWalkCDSignature3, qfg4GuildWalkCDPatch3 },
15290 { false, 340, "Floppy: fix guild tunnel access (3/3)", 1, qfg4GuildWalkFloppySignature3, qfg4GuildWalkFloppyPatch3 },
15291 { true, 440, "fix setLooper calls (1/2)", 1, qfg4SetLooperSignature1, qfg4SetLooperPatch1 },
15292 { true, 475, "fix tarot 3 queen card", 1, qfg4Tarot3QueenSignature, qfg4Tarot3QueenPatch },
15293 { true, 475, "fix tarot 3 death card", 1, qfg4Tarot3DeathSignature, qfg4Tarot3DeathPatch },
15294 { true, 475, "fix tarot 3 two of cups placement", 1, qfg4Tarot3TwoOfCupsSignature, qfg4Tarot3TwoOfCupsPatch },
15295 { true, 475, "fix tarot 3 card priority", 1, qfg4Tarot3PrioritySignature, qfg4Tarot3PriorityPatch },
15296 { true, 475, "fix tarot 5 card priority", 1, qfg4Tarot5PrioritySignature, qfg4Tarot5PriorityPatch },
15297 { false, 500, "CD: fix rope during Igor rescue (1/2)", 1, qfg4GraveyardRopeSignature1, qfg4GraveyardRopePatch1 },
15298 { false, 500, "CD: fix rope during Igor rescue (2/2)", 1, qfg4GraveyardRopeSignature2, qfg4GraveyardRopePatch2 },
15299 { true, 530, "fix setLooper calls (1/2)", 4, qfg4SetLooperSignature1, qfg4SetLooperPatch1 },
15300 { true, 535, "fix setLooper calls (1/2)", 4, qfg4SetLooperSignature1, qfg4SetLooperPatch1 },
15301 { true, 541, "fix setLooper calls (1/2)", 5, qfg4SetLooperSignature1, qfg4SetLooperPatch1 },
15302 { true, 542, "fix setLooper calls (1/2)", 5, qfg4SetLooperSignature1, qfg4SetLooperPatch1 },
15303 { true, 543, "fix setLooper calls (1/2)", 5, qfg4SetLooperSignature1, qfg4SetLooperPatch1 },
15304 { true, 545, "fix setLooper calls (1/2)", 5, qfg4SetLooperSignature1, qfg4SetLooperPatch1 },
15305 { true, 557, "fix forest 557 entry from east", 1, qfg4Forest557PathfindingSignature, qfg4Forest557PathfindingPatch },
15306 { true, 600, "fix passable closed gate after geas", 1, qfg4DungeonGateSignature, qfg4DungeonGatePatch },
15307 { true, 600, "fix gate options after geas", 1, qfg4GateOptionsSignature, qfg4GateOptionsPatch },
15308 { true, 600, "fix paladin's necrotaur message", 1, qfg4NecrotaurMessageSignature, qfg4NecrotaurMessagePatch },
15309 { true, 630, "fix great hall entry from barrel room", 1, qfg4GreatHallEntrySignature, qfg4GreatHallEntryPatch },
15310 { true, 633, "fix stairway pathfinding", 1, qfg4StairwayPathfindingSignature, qfg4StairwayPathfindingPatch },
15311 { true, 633, "Floppy: fix argument message", 1, qfg4ArgumentMessageFloppySignature, qfg4ArgumentMessageFloppyPatch },
15312 { true, 633, "fix room 627 door options", 1, qfg4Room627DoorOptionsSignature, qfg4Room627DoorOptionsPatch },
15313 { true, 633, "fix room 627 door responses (1/2)", 1, qfg4Room627DoorResponsesSignature1, qfg4Room627DoorResponsesPatch1 },
15314 { true, 633, "Floppy: fix room 627 door responses (2/2)", 1, qfg4Room627DoorResponsesFloppySignature2, qfg4Room627DoorResponsesFloppyPatch2 },
15315 { true, 633, "CD: fix room 627 door responses (2/2)", 1, qfg4Room627DoorResponsesCDSignature2, qfg4Room627DoorResponsesCDPatch2 },
15316 { true, 633, "Floppy: fix room 627 door squeak", 1, qfg4Room627SqueakFloppySignature, qfg4Room627SqueakFloppyPatch },
15317 { true, 633, "CD: fix room 627 door squeak", 2, qfg4Room627SqueakCDSignature, qfg4Room627SqueakCDPatch },
15318 { true, 633, "fix great hall keyhole message", 1, qfg4GreatHallKeyholeSignature, qfg4GreatHallKeyholePatch },
15319 { true, 634, "fix room 631 pick lock squeak", 1, qfg4Room631LockSqueakSignature,qfg4Room631LockSqueakPatch },
15320 { true, 640, "fix room 640 pick lock squeak", 1, qfg4Room640LockSqueakSignature,qfg4Room640LockSqueakPatch },
15321 { true, 642, "fix room 642 pick lock squeak", 1, qfg4Room640LockSqueakSignature,qfg4Room640LockSqueakPatch },
15322 { true, 643, "fix room 643 pick lock squeak", 1, qfg4Room643LockSqueakSignature,qfg4Room643LockSqueakPatch },
15323 { true, 643, "fix iron safe's east door sending hero west", 1, qfg4SafeDoorEastSignature, qfg4SafeDoorEastPatch },
15324 { true, 643, "fix iron safe's door oil flags", 1, qfg4SafeDoorOilSignature, qfg4SafeDoorOilPatch },
15325 { true, 644, "fix castle door open message for rogue", 2, qfg4StuckDoorSignature, qfg4StuckDoorPatch },
15326 { true, 644, "fix peer bats, lower door", 1, qfg4LowerPeerBatsSignature, qfg4LowerPeerBatsPatch },
15327 { true, 644, "fix room 644 pick lock squeak", 1, qfg4Room643LockSqueakSignature,qfg4Room644LockSqueakPatch },
15328 { true, 645, "fix extraneous door sound in the castle", 1, qfg4DoubleDoorSoundSignature, qfg4DoubleDoorSoundPatch },
15329 { true, 661, "fix room 661 pick lock squeak", 1, qfg4Room661LockSqueakSignature,qfg4Room661LockSqueakPatch },
15330 { false, 663, "CD: fix crest bookshelf", 1, qfg4CrestBookshelfCDSignature, qfg4CrestBookshelfCDPatch },
15331 { false, 663, "Floppy: fix crest bookshelf", 1, qfg4CrestBookshelfFloppySignature, qfg4CrestBookshelfFloppyPatch },
15332 { true, 663, "CD/Floppy: fix crest bookshelf motion", 1, qfg4CrestBookshelfMotionSignature, qfg4CrestBookshelfMotionPatch },
15333 { true, 663, "CD/Floppy: fix peer bats, upper door (1/2)", 1, qfg4UpperPeerBatsSignature1, qfg4UpperPeerBatsPatch1 },
15334 { false, 663, "CD: fix peer bats, upper door (2/2)", 1, qfg4UpperPeerBatsCDSignature2, qfg4UpperPeerBatsCDPatch2 },
15335 { false, 663, "Floppy: fix peer bats, upper door (2/2)", 1, qfg4UpperPeerBatsFloppySignature2, qfg4UpperPeerBatsFloppyPatch2 },
15336 { true, 670, "fix look dungeon message", 1, qfg4LookDungeonSignature, qfg4LookDungeonPatch },
15337 { true, 710, "fix tentacle wriggle cycler", 1, qfg4TentacleWriggleSignature, qfg4TentacleWrigglePatch },
15338 { true, 710, "fix tentacle retraction for fighter", 1, qfg4PitRopeFighterSignature, qfg4PitRopeFighterPatch },
15339 { true, 710, "fix tentacle retraction for mage (1/2)", 1, qfg4PitRopeMageSignature1, qfg4PitRopeMagePatch1 },
15340 { true, 710, "NRS: fix tentacle retraction for mage (1/2)", 1, qfg4PitRopeMageNrsSignature1, qfg4PitRopeMageNrsPatch1 },
15341 { true, 710, "fix tentacle retraction for mage (2/2)", 1, qfg4PitRopeMageSignature2, qfg4PitRopeMagePatch2 },
15342 { true, 730, "fix ad avis timeout", 1, qfg4AdAvisTimeoutSignature, qfg4AdAvisTimeoutPatch },
15343 { true, 730, "Floppy: fix casting spells at ad avis", 1, qfg4AdAvisSpellsFloppySignature, qfg4AdAvisSpellsFloppyPatch },
15344 { true, 730, "CD: fix casting spells at ad avis", 2, qfg4AdAvisSpellsCDSignature, qfg4AdAvisSpellsCDPatch },
15345 { true, 730, "NRS: fix casting spells at ad avis", 2, qfg4AdAvisSpellsNrsSignature, qfg4AdAvisSpellsNrsPatch },
15346 { true, 730, "fix casting last spell at ad avis", 1, qfg4AdAdvisLastSpellSignature, qfg4AdAdvisLastSpellPatch },
15347 { true, 730, "fix ad avis projectile message", 1, qfg4AdAvisMessageSignature, qfg4AdAvisMessagePatch },
15348 { true, 730, "fix throwing weapons at ad avis", 1, qfg4AdAvisThrowWeaponSignature,qfg4AdAvisThrowWeaponPatch },
15349 { true, 730, "fix fighter's spear animation", 1, qfg4FighterSpearSignature, qfg4FighterSpearPatch },
15350 { true, 770, "fix bone cage teller", 1, qfg4BoneCageTellerSignature, qfg4BoneCageTellerPatch },
15351 { true, 800, "fix setScaler calls", 1, qfg4SetScalerSignature, qfg4SetScalerPatch },
15352 { true, 800, "fix grapnel removing hero's scaler", 1, qfg4RopeScalerSignature, qfg4RopeScalerPatch },
15353 { true, 801, "fix runes puzzle (1/2)", 1, qfg4RunesPuzzleSignature1, qfg4RunesPuzzlePatch1 },
15354 { true, 801, "fix runes puzzle (2/2)", 1, qfg4RunesPuzzleSignature2, qfg4RunesPuzzlePatch2 },
15355 { true, 803, "CD: fix sliding down slope", 1, qfg4SlidingDownSlopeCDSignature, qfg4SlidingDownSlopeCDPatch },
15356 { true, 803, "CD: fix walking up slippery slope", 1, qfg4WalkUpSlopeCDSignature, qfg4WalkUpSlopeCDPatch },
15357 { true, 803, "CD: fix walking down slippery slope", 1, qfg4WalkDownSlopeCDSignature, qfg4WalkDownSlopeCDPatch },
15358 { true, 803, "NRS: fix walking down slippery slope", 1, qfg4WalkDownSlopeNrsSignature, qfg4WalkDownSlopeNrsPatch },
15359 { true, 820, "fix rabbit combat", 1, qfg4RabbitCombatSignature, qfg4RabbitCombatPatch },
15360 { true, 810, "fix conditional void calls", 1, qfg4ConditionalVoidSignature, qfg4ConditionalVoidPatch },
15361 { true, 830, "fix conditional void calls", 2, qfg4ConditionalVoidSignature, qfg4ConditionalVoidPatch },
15362 { true, 835, "fix conditional void calls", 3, qfg4ConditionalVoidSignature, qfg4ConditionalVoidPatch },
15363 { true, 840, "fix conditional void calls", 2, qfg4ConditionalVoidSignature, qfg4ConditionalVoidPatch },
15364 { true, 855, "fix conditional void calls", 1, qfg4ConditionalVoidSignature, qfg4ConditionalVoidPatch },
15365 { true, 870, "fix conditional void calls", 5, qfg4ConditionalVoidSignature, qfg4ConditionalVoidPatch },
15366 { true, 64990, "increase number of save games (1/2)", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
15367 { true, 64990, "increase number of save games (2/2)", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
15368 { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
15369 SCI_SIGNATUREENTRY_TERMINATOR
15370 };
15371
15372 #endif
15373
15374 // ===========================================================================
15375 // script 298 of sq4/floppy has an issue. object "nest" uses another property
15376 // which isn't included in property count. We return 0 in that case, so that
15377 // the game does not increment nest::x. The problem is that the script also
15378 // checks if x exceeds nest::x. We never reach that of course, so the
15379 // incorrect property means that the pterodactyl flight will continue
15380 // endlessly. We could either calculate the property count differently,
15381 // thereby fixing this bug, but I think that just patching it out is cleaner.
15382 // Fixes bug: #5093
15383 static const uint16 sq4FloppySignatureEndlessFlight[] = {
15384 0x39, 0x04, // pushi 04 (selector x)
15385 SIG_MAGICDWORD,
15386 0x78, // push1
15387 0x67, 0x08, // pTos 08 (property x)
15388 0x63, SIG_ADDTOOFFSET(+1), // pToa (invalid property) - 44h for English floppy, 4ch for German floppy
15389 0x02, // add
15390 SIG_END
15391 };
15392
15393 static const uint16 sq4FloppyPatchEndlessFlight[] = {
15394 PATCH_ADDTOOFFSET(+5),
15395 0x35, 0x03, // ldi 03 (which would be the content of the property)
15396 PATCH_END
15397 };
15398
15399 // Floppy-only: When the player tries to throw something at the sequel police
15400 // in Space Quest X (zero g zone), the game will first show a textbox and
15401 // then cause a signature mismatch in ScummVM. In Sierra SCI, it'd crash the
15402 // whole game - or, when the Sierra "patch" got applied, display garbage.
15403 //
15404 // All of this is caused by a typo in the script. Right after the code for
15405 // showing the textbox, there is similar code for showing another textbox, but
15406 // without a pointer to the text. This has to be a typo, because there is no
15407 // unused text to be found within that script.
15408 //
15409 // Sierra's "patch" didn't include a proper fix (as in a modified script).
15410 // Instead they shipped a dummy text resource, which somewhat "solved" the
15411 // issue in Sierra SCI, but it still showed another textbox with garbage in
15412 // it. Funnily Sierra must have known that, because that new text resource
15413 // contains: "Hi! This is a kludge!"
15414 //
15415 // A copy of this script exists in the arcade when the sequel police arrive, but
15416 // it has an additional typo that adds another broken function call to the ATM
15417 // card message. Originally these arcade bugs couldn't be triggered because the
15418 // police didn't respond to clicks due to their description property not being
15419 // set. This Feature behavior was changed in later versions, exposing the bug
15420 // on Mac and Amiga until the script was eventually rewritten for localization.
15421 //
15422 // We properly fix it by removing the faulty code from both scripts, preventing
15423 // crashes in all versions, and by setting an arbitrary sp2:description in
15424 // English PC floppy so that the arcade police respond to clicks as intended.
15425 //
15426 // Applies to: English PC Floppy, English Mac Floppy, English Amiga Floppy
15427 // Responsible methods: sp1::doVerb, heap in script 376
15428 // Fixes bug: found by SCI developer
15429 static const uint16 sq4FloppySignatureThrowStuffAtSequelPolice[] = {
15430 0x47, 0xff, 0x00, 0x02, // calle [export 0 of script 255], 2
15431 0x3a, // toss
15432 SIG_MAGICDWORD,
15433 0x36, // push
15434 0x47, 0xff, 0x00, 0x02, // calle [export 0 of script 255], 2
15435 SIG_END
15436 };
15437
15438 static const uint16 sq4FloppyPatchThrowStuffAtSequelPolice[] = {
15439 PATCH_ADDTOOFFSET(+5),
15440 0x32, PATCH_UINT16(0x0002), // jmp 0002
15441 PATCH_END
15442 };
15443
15444 static const uint16 sq4FloppySignatureClickAtmCardOnSequelPolice[] = {
15445 0x47, 0xff, 0x00, 0x02, // calle [export 0 of script 255], 2
15446 SIG_MAGICDWORD,
15447 0x36, // push
15448 0x47, 0xff, 0x00, 0x02, // calle [export 0 of script 255], 2
15449 SIG_END
15450 };
15451
15452 static const uint16 sq4FloppyPatchClickAtmCardOnSequelPolice[] = {
15453 PATCH_ADDTOOFFSET(+4),
15454 0x32, PATCH_UINT16(0x0002), // jmp 0002
15455 PATCH_END
15456 };
15457
15458 // set an arbitrary sp2:description so that sp2:doVerb can run.
15459 // the description isn't used, it just has to be non-zero.
15460 static const uint16 sq4FloppySignatureSequelPoliceDescription[] = {
15461 SIG_UINT16(0x0000), // description = 0
15462 SIG_UINT16(0x005a), // sighAngle = 90
15463 SIG_UINT16(0x6789), // actions = 26505
15464 SIG_UINT16(0x6789), // onMeCheck = 26505
15465 SIG_UINT16(0x0000), // lookStr = 0
15466 SIG_UINT16(0x0002), // yStep = 2
15467 SIG_MAGICDWORD,
15468 SIG_UINT16(0x0179), // view = 377
15469 SIG_UINT16(0x0004), // view = 4
15470 SIG_END
15471 };
15472
15473 static const uint16 sq4FloppyPatchSequelPoliceDescription[] = {
15474 PATCH_UINT16(0x2b21), // description = "It's one of Vohaul's Sequel Policemen!"
15475 PATCH_END
15476 };
15477
15478 // Right at the start of Space Quest 4 CD, when walking up in the first room, ego will
15479 // immediately walk down just after entering the upper room.
15480 //
15481 // This is caused by the scripts setting ego's vertical coordinate to 189 (BDh), which is the
15482 // trigger in rooms to walk to the room below it. Sometimes this isn't triggered, because
15483 // the scripts also initiate a motion to vertical coordinate 188 (BCh). When you lower the game's speed,
15484 // this bug normally always triggers. And it triggers of course also in the original interpreter.
15485 //
15486 // It doesn't happen in PC floppy, because nsRect is not the same as in CD.
15487 //
15488 // We fix it by setting ego's vertical coordinate to 188 and we also initiate a motion to 187.
15489 //
15490 // Applies to at least: English PC CD
15491 // Responsible method: rm045::doit
15492 // Fixes bug: #5468
15493 static const uint16 sq4CdSignatureWalkInFromBelowRoom45[] = {
15494 0x76, // push0
15495 SIG_MAGICDWORD,
15496 0x78, // push1
15497 0x38, SIG_UINT16(0x00bd), // pushi 00BDh
15498 0x38, SIG_ADDTOOFFSET(+2), // pushi [setMotion selector]
15499 0x39, 0x03, // pushi 3
15500 0x51, SIG_ADDTOOFFSET(+1), // class MoveTo
15501 0x36, // push
15502 0x78, // push1
15503 0x76, // push0
15504 0x81, 0x00, // lag global[0]
15505 0x4a, 0x04, // send 04 -> get ego::x
15506 0x36, // push
15507 0x38, SIG_UINT16(0x00bc), // pushi 00BCh
15508 SIG_END
15509 };
15510
15511 static const uint16 sq4CdPatchWalkInFromBelowRoom45[] = {
15512 PATCH_ADDTOOFFSET(+2),
15513 0x38, PATCH_UINT16(0x00bc), // pushi 00BCh
15514 PATCH_ADDTOOFFSET(+15),
15515 0x38, PATCH_UINT16(0x00bb), // pushi 00BBh
15516 PATCH_END
15517 };
15518
15519 // Sierra seems to have forgotten to include code to play the audio
15520 // describing the "Universal Remote Control" inside the "Hz So good" store.
15521 //
15522 // We add it, so that the audio is now playing.
15523 //
15524 // Applies to at least: English PC CD
15525 // Responsible method: doCatalog::changeState(1Ch) in script 391
15526 // Implements enhancement: #10227
15527 static const uint16 sq4CdSignatureMissingAudioUniversalRemote[] = {
15528 SIG_MAGICDWORD,
15529 0x30, SIG_UINT16(0x00b1), // bnt [skip over state 1Ch code]
15530 0x35, 0x1c, // ldi 1Ch
15531 0x39, 0x0c, // pushi 0Ch
15532 SIG_ADDTOOFFSET(+127), // skip over to substate 1 code of state 1Ch code
15533 0x32, SIG_UINT16(0x0028), // jmp [to toss/ret, substate 0-code]
15534 // substate 1 code
15535 0x3c, // dup
15536 0x35, 0x01, // ldi 01
15537 0x1a, // eq?
15538 0x30, SIG_UINT16(0x0021), // bnt [skip over substate 1 code]
15539 0x39, SIG_SELECTOR8(number), // pushi number
15540 0x78, // push1
15541 0x7a, // push2
15542 0x38, SIG_UINT16(0x0188), // pushi 188h
15543 0x38, SIG_UINT16(0x018b), // pushi 18Bh
15544 0x43, 0x3c, 0x04, // callk Random, 4
15545 0x36, // push
15546 0x39, SIG_SELECTOR8(play), // pushi play
15547 0x76, // push0
15548 0x81, 0x64, // lag global[64h]
15549 0x4a, 0x0a, // send 0Ah
15550 0x38, SIG_SELECTOR16(setScript), // pushi setScript
15551 0x78, // push1
15552 0x72, SIG_UINT16(0x0488), // lofsa startTerminal
15553 0x36, // push
15554 0x63, 0x12, // pToa client
15555 0x4a, 0x06, // send 06
15556 0x3a, // toss
15557 0x33, 0x14, // jmp [to toss/ret]
15558 // state 1Dh, called when returning back to main menu from catalog sub menu
15559 0x3c, // dup
15560 0x35, 0x1d, // ldi 1Dh
15561 0x1a, // eq?
15562 0x31, 0x0e, // bnt [skip last state and toss/ret]
15563 0x35, 0x1d, // ldi 1Dh
15564 0x38, SIG_SELECTOR16(setScript), // pushi setScript
15565 0x78, // push1
15566 0x72, SIG_UINT16(0x0488), // lofsa startTerminal
15567 0x36, // push
15568 0x63, 0x12, // pToa client
15569 0x4a, 0x06, // send 06
15570 0x3a, // toss
15571 0x48, // ret
15572 SIG_END
15573 };
15574
15575 static const uint16 sq4CdPatchMissingAudioUniversalRemote[] = {
15576 0x30, PATCH_UINT16(0x00b7), // bnt [directly to last state code, saving 6 bytes]
15577 0x32, PATCH_UINT16(0x009a), // jmp 154d [to our new code]
15578 // 1 not used byte here
15579 PATCH_ADDTOOFFSET(+128), // skip over to substate 1 code of state 1Ch code
15580 0x32, PATCH_UINT16(0x003f), // substate 0 code, jumping to toss/ret
15581 // directly start with substate 1 code, saving 7 bytes
15582 0x39, PATCH_SELECTOR8(number), // pushi number
15583 0x78, // push1
15584 0x7a, // push2
15585 0x38, PATCH_UINT16(0x0188), // pushi 188h
15586 0x38, PATCH_UINT16(0x018b), // pushi 18Bh
15587 0x43, 0x3c, 0x04, // callk Random, 4
15588 0x36, // push
15589 0x39, PATCH_SELECTOR8(play), // pushi play
15590 0x76, // push0
15591 0x81, 0x64, // lag global[64h]
15592 0x4a, 0x0a, // send 0Ah
15593 0x33, 0x1a, // jmp [state 1Dh directly, saving 12 bytes]
15594 // additional code for playing missing audio (18 bytes w/o jmp back)
15595 0x89, 0x5a, // lsg global[5Ah]
15596 0x35, 0x01, // ldi 1
15597 0x1c, // ne?
15598 0x31, 0x0b, // bnt [skip play audio]
15599 0x38, PATCH_SELECTOR16(say), // pushi say (0123h)
15600 0x78, // push1
15601 0x39, 0x14, // pushi 14h
15602 0x72, PATCH_UINT16(0x0850), // lofsa newRob
15603 0x4a, 0x06, // send 06
15604 // now get back
15605 0x39, 0x0c, // pushi 0Ch
15606 0x32, PATCH_UINT16(0xff50), // jmp back
15607 PATCH_END
15608 };
15609
15610 // It seems that Sierra forgot to set a script flag when cleaning out the bank
15611 // account in Space Quest 4 CD. This was probably caused by the whole bank
15612 // account interaction getting a rewrite and polish in the CD version.
15613 //
15614 // Because of this bug, points for changing back clothes will not get awarded,
15615 // which makes it impossible to get a perfect point score in the CD version of
15616 // the game. The points are awarded by rm371::doit in script 371.
15617 //
15618 // We fix this. PC floppy does not have this bug.
15619 //
15620 // Note: Some Let's Plays on YouTube show points are in fact awarded. But those
15621 // Let's Plays were of a hacked Space Quest 4 version. It was part Floppy,
15622 // part CD version. We consider it to be effectively pirated and not a
15623 // canonical CD version of Space Quest 4. It's easy to identify for having
15624 // both voices and a store called "Radio Shock" instead of "Hz. So Good".
15625 //
15626 // Applies to at least: English PC CD
15627 // Responsible method: but2Script::changeState(2)
15628 // Fixes bug: #6866
15629 static const uint16 sq4CdSignatureGetPointsForChangingBackClothes[] = {
15630 0x35, 0x02, // ldi 02
15631 SIG_MAGICDWORD,
15632 0x1a, // eq?
15633 0x30, SIG_UINT16(0x006a), // bnt [state 3]
15634 0x76,
15635 SIG_ADDTOOFFSET(+46), // jump over "withdraw funds" code
15636 0x33, 0x33, // jmp [end of state 2, set cycles code]
15637 SIG_ADDTOOFFSET(+51), // jump over "clean bank account" code
15638 0x35, 0x02, // ldi 02
15639 0x65, 0x1a, // aTop cycles
15640 0x33, 0x0b, // jmp [toss/ret]
15641 0x3c, // dup
15642 0x35, 0x03, // ldi 03
15643 0x1a, // eq?
15644 0x31, 0x05, // bnt [toss/ret]
15645 SIG_END
15646 };
15647
15648 static const uint16 sq4CdPatchGetPointsForChangingBackClothes[] = {
15649 PATCH_ADDTOOFFSET(+3),
15650 0x30, PATCH_UINT16(0x0070), // bnt [state 3]
15651 PATCH_ADDTOOFFSET(+47), // "withdraw funds" code
15652 0x33, 0x39, // jmp [end of state 2, set cycles code]
15653 PATCH_ADDTOOFFSET(+51),
15654 0x78, // push1
15655 0x39, 0x1d, // pushi 1Dh
15656 0x45, 0x07, 0x02, // callb [export 7 of script 0], 02 (set flag 1Dh - located at global[73h] bit 2)
15657 0x35, 0x02, // ldi 02
15658 0x65, 0x1c, // aTop cycles
15659 0x33, 0x05, // jmp [toss/ret]
15660 // check for state 3 code removed to save 6 bytes
15661 PATCH_END
15662 };
15663
15664 // The English Amiga version contains curious changes to the dress logic in
15665 // Sock's which break the game and weren't included in later versions:
15666 //
15667 // 1. Purchasing the dress is recorded in flag 90 instead of mall:rFlag3
15668 // 2. Flag 90 is cleared when changing clothes after clearing out the ATM
15669 //
15670 // Game flags are global while mall flags are reset upon leaving the mall, which
15671 // makes this look like a bug fix, but Sock's is closed when returning so this
15672 // shouldn't change game logic. Unfortunately Sierra forgot to update the other
15673 // scripts which query mall:rFlag3 and so they never see the dress purchase.
15674 // This creates scenarios where exiting Sock's (room 371) after paying causes
15675 // room 370 to kick Roger out for not paying, preventing game completion.
15676 //
15677 // Clearing the dress-purchase flag has no effect other than to allow purchasing
15678 // the dress a second time as if it never happened, leaving the player without
15679 // enough money to complete the game, having paid for the dress twice.
15680 //
15681 // We fix both bugs by updating the mall scripts so that they all test flag 90
15682 // and never clear it. It's possible the flag change was an optimization, which
15683 // many Amiga tweaks are, since it eliminated a message send in rm371:doit.
15684 //
15685 // Applies to: English Amiga Floppy
15686 // Responsible methods: rm371:doit, rm370:init, warningScript:changeState(1)
15687 // Fixes bug #11004
15688 static const uint16 sq4AmigaSignatureDressPurchaseFlagClear[] = {
15689 SIG_MAGICDWORD,
15690 0x78, // push1
15691 0x39, 0x5a, // pushi 5a
15692 0x45, 0x08, 0x02, // callb proc0_8 02 [ clear flag 90 ]
15693 SIG_END
15694 };
15695
15696 static const uint16 sq4AmigaPatchDressPurchaseFlagClear[] = {
15697 0x32, PATCH_UINT16(0x0003), // jmp 0003 [ don't clear flag 90 ]
15698 PATCH_END
15699 };
15700
15701 static const uint16 sq4AmigaSignatureDressPurchaseFlagCheck[] = {
15702 SIG_MAGICDWORD,
15703 0x36, // push [ mall ]
15704 0x38, SIG_UINT16(0x0228), // pushi rFlag3
15705 0x39, 0x04, // pushi 04
15706 0x46, SIG_UINT16(0x02bc), // calle proc700_3 06 [ is mall:rFlag3 flag 4 set? ]
15707 SIG_UINT16(0x0003), 0x06,
15708 SIG_END
15709 };
15710
15711 static const uint16 sq4AmigaPatchDressPurchaseFlagCheck[] = {
15712 0x78, // push1
15713 0x39, 0x5a, // pushi 5a
15714 0x45, 0x06, 0x02, // callb proc0_6 02 [ is flag 90 set? ]
15715 0x32, PATCH_UINT16(0x0003), // jmp 0003
15716 PATCH_END
15717 };
15718
15719 // The Big And Tall store (room 381) doesn't display its Look message in the CD
15720 // version. We add the missing super:doVerb call to theStore:doVerb.
15721 //
15722 // Applies to: English PC CD
15723 // Responsible method: theStore:doVerb
15724 static const uint16 sq4CdSignatureBigAndTallDescription[] = {
15725 0x3c, // dup
15726 0x35, 0x06, // ldi 06
15727 0x1a, // eq? [ verb == smell ]
15728 0x30, SIG_UINT16(0x0013), // bnt 0013
15729 0x38, SIG_SELECTOR16(modNum), // pushi modNum [ redundant when set to room number ]
15730 0x78, // push1
15731 0x38, SIG_UINT16(0x017d), // pushi 017d
15732 0x38, SIG_SELECTOR16(say), // pushi say
15733 0x78, // push1
15734 0x39, 0x0a, // pushi 0a
15735 0x81, 0x59, // lag 59
15736 0x4a, 0x0c, // send 0c [ sq4GlobalNarrator modNum: 381 say: 10 ]
15737 SIG_MAGICDWORD,
15738 0x33, 0x02, // jmp 02
15739 0x35, 0x00, // ldi 00
15740 0x3a, // toss
15741 SIG_END
15742 };
15743
15744 static const uint16 sq4CdPatchBigAndTallDescription[] = {
15745 0x35, 0x06, // ldi 06
15746 0x1a, // eq? [ verb == smell ]
15747 0x31, 0x0a, // bnt 0a
15748 0x38, PATCH_SELECTOR16(say), // pushi say
15749 0x78, // push1
15750 0x39, 0x0a, // pushi 0a
15751 0x81, 0x59, // lag 59
15752 0x4a, 0x06, // send 06 [ sq4GlobalNarrator say: 10 ]
15753 0x87, 0x01, // lap 01
15754 0x78, // push1
15755 0x1a, // eq? [ verb == look ]
15756 0x31, 0x08, // bnt 08
15757 0x38, PATCH_SELECTOR16(doVerb), // pushi doVerb
15758 0x78, // push1
15759 0x78, // push1
15760 0x57, 0x7a, 0x06, // super Sq4Feature 06 [ super doVerb: 1 ]
15761 PATCH_END
15762 };
15763
15764 // Clicking Do on the Monolith Burger door responds with the message for looking
15765 // at the boss and clicking Taste responds with the taste message for the bush.
15766 // The verb handler is missing the modNum for both and also sets the wrong cond
15767 // for the second.
15768 //
15769 // Applies to: English PC CD
15770 // Responsible method: door:doVerb(4)
15771 // Fixes bug #10976
15772 static const uint16 sq4CdSignatureMonolithBurgerDoor[] = {
15773 SIG_MAGICDWORD,
15774 0x31, 0x13, // bnt 13
15775 0x38, SIG_SELECTOR16(modNum), // pushi modNum
15776 0x78, // push1
15777 0x38, SIG_UINT16(0x0177), // pushi 0177
15778 0x38, SIG_SELECTOR16(say), // pushi say
15779 0x78, // push1
15780 0x78, // push1
15781 0x81, 0x59, // lag 59
15782 0x4a, 0x0c, // send 0c [ Sq4GlobalNarrator modNum 375: say: 1 ]
15783 SIG_ADDTOOFFSET(+9),
15784 0x38, SIG_SELECTOR16(say), // pushi say
15785 0x78, // push1
15786 0x7a, // push2
15787 0x81, 0x59, // lag 59
15788 0x4a, 0x06, // send 06 [ Sq4GlobalNarrator say: 2 ]
15789 SIG_ADDTOOFFSET(+25),
15790 0x38, SIG_SELECTOR16(say), // pushi say
15791 0x78, // push1
15792 0x39, 0x0b, // pushi 0b
15793 0x81, 0x59, // lag 59
15794 0x4a, 0x06, // send 06 [ Sq4GlobalNarrator say: 11 ]
15795 SIG_END
15796 };
15797
15798 static const uint16 sq4CdPatchMonolithBurgerDoor[] = {
15799 PATCH_ADDTOOFFSET(+13),
15800 0x36, // push
15801 PATCH_ADDTOOFFSET(+13),
15802 0x35, 0x02, // ldi 02
15803 0x33, 0xe3, // jmp e3 [ Sq4GlobalNarrator modNum 375: say: 2 ]
15804 PATCH_ADDTOOFFSET(+30),
15805 0x35, 0x04, // ldi 04
15806 0x33, 0xc1, // jmp c1 [ Sq4GlobalNarrator modNum 375: say: 4 ]
15807 PATCH_END
15808 };
15809
15810 // For Space Quest 4 CD, Sierra added a pick up animation for Roger when he
15811 // picks up the rope.
15812 //
15813 // When the player is detected by the zombie right at the start of the game,
15814 // while picking up the rope, scripts bomb out.
15815 //
15816 // This is caused by code intended to make Roger face the arriving drone. We
15817 // fix it by checking if ego::cycler is actually set before calling that code.
15818 //
15819 // Applies to at least: English PC CD
15820 // Responsible method: droidShoots::changeState(3)
15821 // Fixes bug: #6076
15822 static const uint16 sq4CdSignatureGettingShotWhileGettingRope[] = {
15823 0x35, 0x02, // ldi 02
15824 0x65, 0x1a, // aTop cycles
15825 0x32, SIG_UINT16(0x02fa), // jmp [end]
15826 SIG_MAGICDWORD,
15827 0x3c, // dup
15828 0x35, 0x02, // ldi 02
15829 0x1a, // eq?
15830 0x31, 0x0b, // bnt [state 3 check]
15831 0x76, // push0
15832 0x45, 0x02, 0x00, // callb [export 2 of script 0], 00 (disable controls)
15833 0x35, 0x02, // ldi 02
15834 0x65, 0x1a, // aTop cycles
15835 0x32, SIG_UINT16(0x02e9), // jmp [end]
15836 0x3c, // dup
15837 0x35, 0x03, // ldi 03
15838 0x1a, // eq?
15839 0x31, 0x1e, // bnt [state 4 check]
15840 0x76, // push0
15841 0x45, 0x02, 0x00, // callb [export 2 of script 0], 00 (disable controls again??)
15842 0x7a, // push2
15843 0x89, 0x00, // lsg global[0]
15844 0x72, SIG_UINT16(0x0242), // lofsa deathDroid
15845 0x36, // push
15846 0x45, 0x0d, 0x04, // callb [export 13 of script 0], 04 (set heading of ego to face droid)
15847 SIG_END
15848 };
15849
15850 static const uint16 sq4CdPatchGettingShotWhileGettingRope[] = {
15851 PATCH_ADDTOOFFSET(+11),
15852 // this makes state 2 only do the 2 cycles wait, controls should always be disabled already at this point
15853 0x2f, 0xf3, // bt [previous state aTop cycles code]
15854 // Now we check for state 3, this change saves us 11 bytes
15855 0x3c, // dup
15856 0x35, 0x03, // ldi 03
15857 0x1a, // eq?
15858 0x31, 0x29, // bnt [state 4 check]
15859 // new state 3 code
15860 0x76, // push0
15861 0x45, 0x02, 0x00, // callb [export 2 of script 0], 00 (disable controls, actually not needed)
15862 0x38, PATCH_SELECTOR16(cycler), // pushi cycler
15863 0x76, // push0
15864 0x81, 0x00, // lag global[0]
15865 0x4a, 0x04, // send 04 (get ego::cycler)
15866 0x30, PATCH_UINT16(0x000a), // bnt [skip the heading call]
15867 PATCH_END
15868 };
15869
15870 // During the SQ4 introduction logo, EGA versions increase the number of calls
15871 // to kPaletteAnimate by 40x. This was probably to achieve the same delay as
15872 // VGA even though no palette animation occurred. This adjustment interferes
15873 // with our kPaletteAnimate speed throttling for SQ4 scripts such as this, bug
15874 // #6057. We remove the EGA delay, making all versions consistent, otherwise
15875 // the logo is displayed for over 3 minutes instead of 5 seconds.
15876 //
15877 // Applies to: English PC EGA Floppy, Japanese PC-98
15878 // Responsible method: rmScript:changeState
15879 // Fixes bug #6193
15880 static const uint16 sq4SignatureEgaIntroDelay[] = {
15881 SIG_MAGICDWORD,
15882 0x89, 0x69, // lsg 69 [ system colors ]
15883 0x35, 0x10, // ldi 10
15884 0x1e, // gt? [ system colors > 16 ]
15885 0x30, // bnt [ use EGA delay ]
15886 SIG_END
15887 };
15888
15889 static const uint16 sq4PatchEgaIntroDelay[] = {
15890 0x33, 0x06, // jmp 06 [ don't use EGA delay ]
15891 PATCH_END
15892 };
15893
15894 // Talking to the red shopper in the mall has a 5% chance of a funny message but
15895 // this script is broken in the CD version. After the first time the wrong
15896 // message tuple is attempted by the narrator as its modNum value is cleared.
15897 // The message text is also accidentally repeated in a message box.
15898 //
15899 // We fix this by specifying the modNum when saying the message and removing
15900 // the erroneous message box call.
15901 //
15902 // Applies to: English PC CD
15903 // Responsible method: shopper3:doVerb(2)
15904 // Fixes bug #10911
15905 static const uint16 sq4CdSignatureRedShopperMessageFix[] = {
15906 0x38, SIG_SELECTOR16(say), // pushi say
15907 0x78, // push1
15908 SIG_MAGICDWORD,
15909 0x78, // push1
15910 0x72, SIG_UINT16(0x057a), // lofsa wierdNar
15911 0x4a, 0x06, // send 06 [ wierdNar say: 1 ]
15912 0x78, // push1
15913 0x72, SIG_UINT16(0x0660), // lofsa "Mr. Carlos sent me..."
15914 0x36, // push
15915 0x46, SIG_UINT16(0x0399), // calle proc921_0 [ message box ]
15916 SIG_UINT16(0x0000), 0x02,
15917 SIG_END
15918 };
15919
15920 static const uint16 sq4CdPatchRedShopperMessageFix[] = {
15921 0x38, PATCH_SELECTOR16(modNum), // pushi modNum
15922 0x38, PATCH_UINT16(0x0001), // pushi 0001
15923 0x38, PATCH_UINT16(0x02bc), // pushi 02bc
15924 0x38, PATCH_SELECTOR16(say), // pushi say
15925 0x39, 0x01, // pushi 01
15926 0x39, 0x01, // pushi 01
15927 0x72, PATCH_UINT16(0x057a), // lofsa wierdNar
15928 0x4a, 0x0c, // send 0c [ wierdNar modNum: 700 say: 1 ]
15929 PATCH_END
15930 };
15931
15932 // When swimming in zero gravity in the mall, the game can lock up if the Sequel
15933 // Police shoot while ego swims past the edge of the screen.
15934 //
15935 // When the Sequel Police shoot, the object "blast" animates near ego. blast is
15936 // an obstacle that ego can collide with. If ego is shot at while going beyond
15937 // the edge of the screen and blast is in the right position then stayInScript
15938 // can lock up due to ego getting stuck on the invisible blast object and never
15939 // reaching his destination.
15940 //
15941 // We fix this by setting blast's ignore-actors flag so that ego can't collide
15942 // with it and get stuck. This does not affect whether or not ego gets shot.
15943 //
15944 // Applies to at least: English PC Floppy, English PC CD, probably all versions
15945 // Responsible method: Heap in scripts 405, 406, 410, and 411
15946 // Fixes bug #10912
15947 static const uint16 sq4SignatureZeroGravityBlast[] = {
15948 SIG_MAGICDWORD, // blast
15949 SIG_UINT16(0x0002), // yStep = 2
15950 SIG_UINT16(0x001c), // view = 128
15951 SIG_UINT16(0x0000), // loop = 0
15952 SIG_UINT16(0x0000), // cel = 0
15953 SIG_UINT16(0x0000), // priority = 0
15954 SIG_UINT16(0x0000), // underBits = 0
15955 SIG_UINT16(0x0000), // signal = 0
15956 SIG_END
15957 };
15958
15959 static const uint16 sq4PatchZeroGravityBlast[] = {
15960 PATCH_ADDTOOFFSET(+12),
15961 PATCH_UINT16(0x4000), // signal = $4000 [ set ignore-actors flag ]
15962 PATCH_END
15963 };
15964
15965 // Cedric the owl from KQ5 appears in the CD version of Ms. Astro Chicken, but a
15966 // bug in this easter egg makes it a much rarer occurrence than intended.
15967 //
15968 // Every 50 ticks there's a 1 in 21 chance of Cedric appearing if there's no
15969 // rock on screen and he hasn't already been killed by colliding with the
15970 // player or getting shot by the farmer. The problem is that unless Cedric
15971 // appears before the farmer, which is very unlikely, then the farmer's first
15972 // bullet will kill Cedric off-screen due to incorrect collision testing.
15973 // buckShot:doit tests for collision by calling cedric:onMe, but Cedric isn't
15974 // initialized until he first appears, and View:onMe always returns true no
15975 // matter what coordinates are being tested if its rectangle isn't initialized.
15976 //
15977 // We fix this by initializing Cedric's actor-hidden signal flag on the heap.
15978 // This prevents View:onMe from returning true before Cedric is initialized.
15979 // The flag is later cleared by cedric:init when he is placed on screen.
15980 //
15981 // Applies to: English PC CD
15982 // Responsible method: Heap in script 290
15983 // Fixes bug #10920
15984 static const uint16 sq4CdSignatureCedricEasterEgg[] = {
15985 SIG_MAGICDWORD, // cedric
15986 SIG_UINT16(0x0110), // view = 272
15987 SIG_UINT16(0x0000), // loop = 0
15988 SIG_UINT16(0x0000), // cel = 0
15989 SIG_UINT16(0x000d), // priority = 13
15990 SIG_UINT16(0x0000), // underBits = 0
15991 SIG_UINT16(0x0810), // signal = $0810
15992 SIG_END
15993 };
15994
15995 static const uint16 sq4CdPatchCedricEasterEgg[] = {
15996 PATCH_ADDTOOFFSET(+10),
15997 PATCH_UINT16(0x0890), // signal = $0890 [ set actor-hidden flag ]
15998 PATCH_END
15999 };
16000
16001 // Colliding with Cedric in Ms. Astro Chicken after colliding with an obstacle
16002 // locks up the game. cedric:doit doesn't check if the player has been hit
16003 // before running killCedricScript. This interferes with the collision scripts'
16004 // animations and prevents them from continuing.
16005 //
16006 // We fix this by not running killCedricScript if the player has been hit.
16007 // Unfortunately there's no single property that can be tested as each
16008 // collision script does things differently, so this is a two-part patch.
16009 // First, ScrollActor:doChicken is patched to set User:canControl to 0, making
16010 // it consistent with the other collision scripts. Second, cedric:doit is
16011 // patched to test this value. To make room for this we replace testing
16012 // killCedricScript with testing the flag that killCedricScript sets.
16013 //
16014 // Applies to: English PC CD
16015 // Responsible methods: ScrollActor:doChicken, cedric:doit
16016 // Fixes bug #10920
16017 static const uint16 sq4CdSignatureCedricLockup1[] = {
16018 SIG_MAGICDWORD,
16019 0x18, // not
16020 0x30, SIG_UINT16(0x0049), // bnt 0049 [ end of method ]
16021 0x63, 0x84, // pToa deathLoop
16022 0x30, SIG_UINT16(0x0044), // bnt 0044 [ end of method ]
16023 0x38, SIG_SELECTOR16(stop), // pushi stop
16024 0x76, // push0
16025 0x81, 0x64, // lag 64
16026 0x4a, 0x04, // send 04 [ longSong2 stop: ]
16027 0x38, SIG_SELECTOR16(stop), // pushi stop
16028 0x76, // push0
16029 0x72, SIG_UINT16(0x0108), // lofsa eggSplatting
16030 0x4a, 0x04, // send 04 [ eggSplatting stop: ]
16031 0x39, SIG_SELECTOR8(number), // pushi number
16032 0x78, // push1
16033 0x67, 0x86, // pTos deathMusic
16034 0x39, SIG_SELECTOR8(loop), // pushi loop
16035 0x78, // push1
16036 0x78, // push1 [ unnecessary, loop is initialized to 1 on heap ]
16037 SIG_ADDTOOFFSET(+7),
16038 0x4a, 0x12, // send 12 [ theSound number: deathMusic loop: 1 play: self ]
16039 SIG_END
16040 };
16041
16042 static const uint16 sq4CdPatchCedricLockup1[] = {
16043 0x2f, 0x4b, // bt 4b [ end of method ]
16044 0x63, 0x84, // pToa deathLoop
16045 0x31, 0x47, // bnt 47 [ end of method ]
16046 0x38, PATCH_SELECTOR16(stop), // pushi stop
16047 0x3c, // dup
16048 0x76, // push0
16049 0x81, 0x64, // lag 64
16050 0x4a, 0x04, // send 04 [ longSong2 stop: ]
16051 0x76, // push0
16052 0x72, PATCH_UINT16(0x0108), // lofsa eggSplatting
16053 0x4a, 0x04, // send 04 [ eggSplatting stop: ]
16054 0x39, PATCH_SELECTOR8(number), // pushi number
16055 0x78, // push1
16056 0x67, 0x86, // pTos deathMusic
16057 0x38, PATCH_SELECTOR16(canControl), // pushi canControl
16058 0x78, // push1
16059 0x76, // push0
16060 0x81, 0x50, // lag 50
16061 0x4a, 0x06, // send 06 [ User canControl: 0 ]
16062 PATCH_ADDTOOFFSET(+7),
16063 0x4a, 0x0c, // send 0c [ theSound number: deathMusic play: self ]
16064 PATCH_END
16065 };
16066
16067 static const uint16 sq4CdSignatureCedricLockup2[] = {
16068 SIG_MAGICDWORD,
16069 0x31, 0x17, // bnt 17 [ end of method ]
16070 0x38, SIG_SELECTOR16(script), // pushi script
16071 0x76, // push0
16072 0x51, 0x9c, // class astroChicken
16073 0x4a, 0x04, // send 04 [ astroChicken script? ]
16074 0x18, // not [ acc = 1 if killCedricScript not running ]
16075 0x31, 0x0c, // bnt 0c [ end of method ]
16076 0x38, SIG_SELECTOR16(setScript), // pushi setScript
16077 0x78, // push1
16078 0x72, SIG_UINT16(0x0f7c), // lofsa killCedricScript
16079 0x36, // push
16080 0x51, 0x9c, // class astroChicken
16081 0x4a, 0x06, // send 06 [ astroChicken setScript: killCedricScript ]
16082 0x48, // ret
16083 0x48, // ret
16084 SIG_END
16085 };
16086
16087 static const uint16 sq4CdPatchCedricLockup2[] = {
16088 0x31, 0x18, // bnt 18 [ end of method ]
16089 0x38, PATCH_SELECTOR16(canControl), // pushi canControl
16090 0x76, // push0
16091 0x81, 0x50, // lag 50
16092 0x4a, 0x04, // send 04 [ User canControl? ]
16093 0x8b, 0x21, // lsl 21 [ local33 = 0 if cedric is alive, 1 if dead ]
16094 0x22, // lt? [ acc = 1 if cedric is alive and user has control ]
16095 0x31, 0x0b, // bnt 0b [ end of method ]
16096 0x38, PATCH_SELECTOR16(setScript), // pushi setScript
16097 0x78, // push1
16098 0x74, PATCH_UINT16(0x0f7c), // lofss killCedricScript
16099 0x51, 0x9c, // class astroChicken
16100 0x4a, 0x06, // send 06 [ astroChicken setScript: killCedricScript ]
16101 PATCH_END
16102 };
16103
16104 // Dodging a biker in Ulence Flats before they've reached their first checkpoint
16105 // causes the player to prematurely regain control, allowing them to break the
16106 // game by walking to another room before the dodge sequence completes. This is
16107 // due to the biker scripts in each room having an unnecessary handsOn call, so
16108 // we remove it. This does not reduce the time that the player has to react,
16109 // the scripts theDodgeL and theDodgeR call handsOn when ego crouches.
16110 //
16111 // Biker bugs like this only occur in the CD version due to its slower bikes.
16112 //
16113 // Applies to: English PC CD
16114 // Responsible methods: runOverScript1-3:changeState, runOver:changeState,
16115 // runOver2:changeState, runOverScript:changeState
16116 // Fixes bug #9806
16117 static const uint16 sq4CdSignatureBikerHandsOn[] = {
16118 0x38, SIG_UINT16(0x02bb), // pushi status [ hard-coded for CD ]
16119 0x78, // push1
16120 SIG_MAGICDWORD,
16121 0x39, 0x04, // pushi 04
16122 0x51, 0x98, // class ulence
16123 0x4a, 0x06, // send 06 [ ulence status: 4 ]
16124 0x76, // push0
16125 0x45, 0x03, 0x00, // callb proc0_3 [ handsOn ]
16126 SIG_END
16127 };
16128
16129 static const uint16 sq4CdPatchBikerHandsOn[] = {
16130 PATCH_ADDTOOFFSET(+10),
16131 0x33, 0x02, // jmp 02 [ skip handsOn ]
16132 PATCH_END
16133 };
16134
16135 // Clicking Look/Do/etc on an object in Ulence Flats while a biker approaches
16136 // can bring ego out of crouch-mode and allow the player to break the game by
16137 // walking to another room before the dodge sequence completes. This occurs if
16138 // ego isn't facing the object. Ego's heading will be changed and ultimately
16139 // stopGroop:doit will reset ego's view to walking.
16140 //
16141 // We fix this by clearing ego:looper when placing ego into crouch mode. This
16142 // causes Actor:setHeading to instead use kDirLoop which respects the flag
16143 // kSignalDoesntTurn. ego:looper is automatically restored after dodging.
16144 //
16145 // Applies to: English PC CD
16146 // Responsible methods: theDodgeR:changeState, theDodgeL:changeState
16147 // Fixes bug #9806
16148 static const uint16 sq4CdSignatureBikerCrouchVerb[] = {
16149 0x35, 0x01, // ldi 01
16150 0x1a, // eq?
16151 0x31, 0x1a, // bnt 1a [ state 2 ]
16152 0x76, // push0
16153 SIG_MAGICDWORD,
16154 0x45, 0x03, 0x00, // callb proc0_3 [ handsOn ]
16155 0x7a, // push2 [ view ]
16156 0x78, // push1
16157 0x38, SIG_UINT16(0x027b), // pushi 027b [ crouch ]
16158 0x38, SIG_SELECTOR16(setLoop), // pushi setLoop
16159 0x78, // push1
16160 SIG_ADDTOOFFSET(+1), // push0 in theDodgeR, push1 in theDodgeL
16161 0x38, SIG_SELECTOR16(setCel), // pushi setCel
16162 0x78, // push1
16163 0x76, // push0
16164 0x81, 0x00, // lag 00
16165 0x4a, 0x12, // send 12 [ ego view: 635 setLoop: * setCel: 0 ]
16166 0x32, // jmp [ end of method ]
16167 SIG_END
16168 };
16169
16170 static const uint16 sq4CdPatchBikerCrouchVerb[] = {
16171 0x18, // not [ save a byte ]
16172 0x1a, // eq?
16173 0x31, 0x1b, // bnt 1b [ state 2 ]
16174 0x76, // push0
16175 0x39, PATCH_SELECTOR8(looper), // pushi looper
16176 0x78, // push1
16177 0x76, // push0
16178 PATCH_ADDTOOFFSET(+17),
16179 0x4a, 0x18, // send 18 [ ego looper: 0 view: 635 setLoop: * setCel: 0 ]
16180 0x45, 0x03, 0x00, // callb proc0_3 [ handsOn ]
16181 PATCH_END
16182 };
16183
16184 // Clicking Do on the Ulence Flats bar door in room 610 while a biker approaches
16185 // causes ego to enter the bar before the dodge sequence completes, breaking
16186 // the game. Sierra forgot to test ulence:egoBusy as they did when clicking on
16187 // the timepod in room 613.
16188 //
16189 // We fix this by testing ulence:egoBusy before running enterBar in door:doVerb.
16190 // Fortunately there is already an unnecessary script test we can overwrite.
16191 // This script test was copied from rm610:doit where it is necessary.
16192 //
16193 // Applies to: English PC CD
16194 // Responsible method: door:doVerb(4)
16195 // Fixes bug #9806
16196 static const uint16 sq4CdSignatureBikerBarDoor[] = {
16197 0x38, SIG_SELECTOR16(script), // pushi script
16198 0x76, // push0
16199 0x81, 0x02, // lag 02
16200 0x4a, 0x04, // send 04 [ rm610 script? ]
16201 0x36, // push
16202 0x72, SIG_UINT16(0x014a), // lofsa enterBar
16203 SIG_MAGICDWORD,
16204 0x1a, // eq? [ rm610:script == enterBar ]
16205 0x18, // not
16206 0x30, SIG_UINT16(0x0019), // bnt 0019 [ don't run enterBar ]
16207 SIG_END
16208 };
16209
16210 static const uint16 sq4CdPatchBikerBarDoor[] = {
16211 0x38, PATCH_UINT16(0x02cc), // pushi egoBusy [ hard-coded for CD ]
16212 0x76, // push0
16213 0x51, 0x98, // class ulence
16214 0x4a, 0x04, // send 04 [ ulence egoBusy? ]
16215 0x18, // not
16216 0x32, PATCH_UINT16(0x0002), // jmp 0002
16217 PATCH_END
16218 };
16219
16220 // Clicking Do on the timepod in room 613 while a biker approaches always shows
16221 // "Not now!" in a message box, even in speech mode. It seems that Sierra
16222 // thought they didn't have audio for this message and created a text resource
16223 // just for it, but it also appears in room 90 with audio, so we use that.
16224 //
16225 // Applies to: English PC CD
16226 // Responsible method: ship:doVerb(4)
16227 // Fixes bug #10922
16228 static const uint16 sq4CdSignatureBikerTimepodMessage[] = {
16229 0x36, // push
16230 0x35, 0x01, // ldi 01
16231 0x1a, // eq?
16232 0x31, 0x0d, // bnt 0d [ skip if ulence:egoBusy != 1 ]
16233 0x7a, // push2
16234 0x38, SIG_UINT16(0x0265), // pushi 0265
16235 0x76, // push0
16236 SIG_MAGICDWORD,
16237 0x46, SIG_UINT16(0x0330), // calle proc816_1 04 [ message box ]
16238 SIG_UINT16(0x0001), 0x04,
16239 SIG_END
16240 };
16241
16242 static const uint16 sq4CdPatchBikerTimepodMessage[] = {
16243 0x31, 0x11, // bnt 11 [ skip if ulence:egoBusy == 0 ]
16244 0x38, PATCH_SELECTOR16(modNum), // pushi modNum
16245 0x78, // push1
16246 0x39, 0x5a, // pushi 5a
16247 0x38, PATCH_SELECTOR16(say), // pushi say
16248 0x78, // push1
16249 0x7a, // push2
16250 0x81, 0x59, // lag 59
16251 0x4a, 0x0c, // send 0c [ Sq4GlobalNarrator modNum: 90 say: 2 ]
16252 PATCH_END
16253 };
16254
16255 // Hiding from the Sequel Police in the electronics store (room 390) locks up
16256 // the CD version and has a subsequent animation bug.
16257 //
16258 // This scene is triggered by going east to the escalator after the police
16259 // arrive and then back to the electronics store. Police appear on both sides
16260 // and the only way to survive is to hide in the store while they talk before
16261 // splitting up. Their first message fails to set a caller, locking up the
16262 // game, and their other messages contain typos and newline characters left
16263 // over from floppy versions.
16264 //
16265 // We fix the lockup by passing the missing "self" parameter so that the script
16266 // proceeds. This exposes the next bug, where the police walk to the beltways
16267 // and instead of standing, shoot wildly into the mall. The script doesn't
16268 // remove their Walk cyclers and so they continue to animate. This worked in
16269 // floppy versions but the Cycle classes were upgraded in CD with different
16270 // behavior. We fix this by removing the Walk cyclers.
16271 //
16272 // Applies to: English PC CD
16273 // Responsible method: sp1Squeeze:changeState
16274 // Fixes bug #10977
16275 static const uint16 sq4CdSignatureHzSoGoodSequelPoliceLockup[] = {
16276 0x38, SIG_SELECTOR16(say), // pushi say
16277 0x78, // push1
16278 SIG_MAGICDWORD,
16279 0x78, // push1
16280 0x72, SIG_UINT16(0x0630), // lofsa tSP1
16281 0x4a, 0x06, // send 06 [ tSP1 say: 1 ]
16282 0x32, SIG_UINT16(0x01c1), // jmp 01c1 [ end of method ]
16283 SIG_END
16284 };
16285
16286 static const uint16 sq4CdPatchHzSoGoodSequelPoliceLockup[] = {
16287 PATCH_ADDTOOFFSET(+3),
16288 0x7a, // push2
16289 PATCH_ADDTOOFFSET(+4),
16290 0x7c, // pushSelf
16291 0x4a, 0x08, // send 06 [ tSP1 say: 1 self ]
16292 0x3a, // toss
16293 0x48, // ret
16294 PATCH_END
16295 };
16296
16297 static const uint16 sq4CdSignatureHzSoGoodSequelPoliceCycler[] = {
16298 0x31, 0x20, // bnt 20 [ state 12 ]
16299 SIG_ADDTOOFFSET(+29),
16300 SIG_MAGICDWORD,
16301 0x32, SIG_UINT16(0x00a9), // jmp 00a9 [ end of method ]
16302 0x3c, // dup
16303 0x35, 0x0c, // ldi 0c
16304 0x1a, // eq?
16305 0x30, SIG_UINT16(0x0030), // bnt 0030 [ state 13 ]
16306 0x7a, // push2 [ view ]
16307 0x78, // push1
16308 0x39, 0x0d, // pushi 0d
16309 0x38, SIG_SELECTOR16(setLoop), // pushi setLoop
16310 SIG_ADDTOOFFSET(+32),
16311 0x4a, 0x24, // send 24 [ sp1 view: 13 setLoop: ... ]
16312 SIG_ADDTOOFFSET(+11),
16313 0x31, 0x21, // bnt 21 [ state 14 ]
16314 SIG_ADDTOOFFSET(+30),
16315 0x32, SIG_UINT16(0x004b), // jmp 004b [ end of method ]
16316 0x3c, // dup
16317 0x35, 0x0e, // ldi 0e
16318 0x1a, // eq?
16319 0x30, SIG_UINT16(0x0030), // bnt 0030 [ state 15 ]
16320 0x7a, // push2 [ view ]
16321 0x78, // push1
16322 0x39, 0x0d, // pushi 0d
16323 0x38, SIG_SELECTOR16(setLoop), // pushi setLoop
16324 SIG_ADDTOOFFSET(+37),
16325 0x4a, 0x26, // send 26 [ sp2 view: 13 setLoop: ... ]
16326 SIG_END
16327 };
16328
16329 static const uint16 sq4CdPatchHzSoGoodSequelPoliceCycler[] = {
16330 0x31, 0x1d, // bnt 1d [ state 12 ]
16331 PATCH_ADDTOOFFSET(+29),
16332 0x3c, // dup
16333 0x35, 0x0c, // ldi 0c
16334 0x1a, // eq?
16335 0x31, 0x34, // bnt 34 [ state 13 ]
16336 0x38, PATCH_SELECTOR16(setCycle), // pushi setCycle
16337 0x78, // push1
16338 0x76, // push0
16339 0x7a, // push2 [ view ]
16340 0x78, // push1
16341 0x39, 0x0d, // pushi 0d
16342 0x39, PATCH_SELECTOR8(loop), // pushi loop
16343 PATCH_ADDTOOFFSET(+32),
16344 0x4a, 0x2a, // send 2a [ sp1 setCycle: 0 view: 13 loop: ... ]
16345 PATCH_ADDTOOFFSET(+11),
16346 0x31, 0x1e, // bnt 1e [ state 14 ]
16347 PATCH_ADDTOOFFSET(+30),
16348 0x3c, // dup
16349 0x35, 0x0e, // ldi 0e
16350 0x1a, // eq?
16351 0x31, 0x34, // bnt 34 [ state 15 ]
16352 0x38, PATCH_SELECTOR16(setCycle), // pushi setCycle
16353 0x78, // push1
16354 0x76, // push0
16355 0x7a, // push2 [ view ]
16356 0x78, // push1
16357 0x39, 0x0d, // pushi 0d
16358 0x39, PATCH_SELECTOR8(loop), // pushi loop
16359 PATCH_ADDTOOFFSET(+37),
16360 0x4a, 0x2c, // send 2c [ sp2 setCycle: 0 view: 13 loop: ... ]
16361 PATCH_END
16362 };
16363
16364 // Room 370 in front of Sock's is missing a flag check and runs a lethal Sequel
16365 // Police script when escaping the Skate-O-Rama after hiding in the electronics
16366 // store. A lockup bug in the CD version prevented getting this far. As this is
16367 // the wrong time to run the script, the police are drawn as if they're still
16368 // swimming in zero gravity.
16369 //
16370 // Flag 23 is set when the police pursue in escalator room 400 and triggers the
16371 // squeeze scripts that force entering the Skate-O-Rama in rooms 370 and 390.
16372 // Flag 22 is set when exiting the Skate-O-Rama after dodging the police. The
16373 // two rooms handle these flags differently and are out of sync. Room 370
16374 // clears flag 23 and doesn't test 22 while room 390 doesn't clear 23 and does
16375 // test 22. The result is that room 370 doesn't see that you've escaped the
16376 // Skate-O-Rama and assumes the police are still pursuing from room 400.
16377 //
16378 // We fix this by only running sp1Squeeze in room 370 when coming from room 400.
16379 // This is equivalent to adding the missing flag 22 check but can be done in
16380 // the available bytes. To make room we use the clear-flag method's return
16381 // value, which is the previous flag value, and only test if the previous room
16382 // number is even, which only room 400 is. This patch is split into two parts
16383 // to surround a bnt instruction whose operand size changed between versions.
16384 //
16385 // Applies to: All versions
16386 // Responsible method: rm570:init
16387 // Fixes bug #10977
16388 static const uint16 sq4SignatureSocksSequelPoliceFlag1[] = {
16389 0x78, // push1
16390 SIG_MAGICDWORD,
16391 0x39, 0x17, // pushi 17
16392 0x45, 0x06, 0x02, // callb proc0_6 [ is flag 23 set? ]
16393 SIG_END
16394 };
16395
16396 static const uint16 sq4PatchSocksSequelPoliceFlag1[] = {
16397 PATCH_ADDTOOFFSET(+3),
16398 0x45, 0x08, 0x02, // callb proc0_8 [ clear flag 23, was flag set? ]
16399 PATCH_END
16400 };
16401
16402 static const uint16 sq4SignatureSocksSequelPoliceFlag2[] = {
16403 0x78, // push1
16404 SIG_MAGICDWORD,
16405 0x39, 0x17, // pushi 17
16406 0x45, 0x08, 0x02, // callb proc0_8 [ clear flag 23 ]
16407 SIG_ADDTOOFFSET(+0x27),
16408 0x38, SIG_SELECTOR16(setRegions), // pushi setRegions
16409 SIG_END
16410 };
16411
16412 static const uint16 sq4PatchSocksSequelPoliceFlag2[] = {
16413 0x81, 0x0c, // lag 0c
16414 0x78, // push1
16415 0x12, // and [ is previous room number odd? ]
16416 0x2f, 0x27, // bt 27 [ skip sp1Squeeze ]
16417 PATCH_END
16418 };
16419
16420 // Clicking Walk while getting shot by the Sequel Police outside of Sock's in
16421 // room 370 crashes the CD version. This causes an Oops! error in the original.
16422 // The lookupSelector error comes from within the Grooper and Grycler classes
16423 // but the real bug is that this room's script fails to call handsOff, allowing
16424 // movement during ego's death animation, unlike all the other laser scripts.
16425 //
16426 // We prevent the crash by adding the missing handsOff call.
16427 //
16428 // Applies to: English PC CD
16429 // Responsible method: sp2Squeeze:changeState(3)
16430 // Fixes bug #10974
16431 static const uint16 sq4CdSignatureSocksSequelPoliceHandsOff[] = {
16432 0x76, // push0 [ y ]
16433 0x76, // push0
16434 0x81, 0x00, // lag 00
16435 0x4a, 0x04, // send 04 [ ego y? ]
16436 SIG_MAGICDWORD,
16437 0x36, // push
16438 0x35, 0x20, // ldi 20
16439 0x04, // sub
16440 0xa3, 0x00, // sal 00 [ local0 = ego:y ]
16441 0x38, SIG_SELECTOR16(setMotion), // pushi setMotion
16442 0x78, // push1
16443 0x76, // push0
16444 0x81, 0x00, // lag 00
16445 0x4a, 0x06, // send 06 [ ego setMotion: 0 ]
16446 SIG_END
16447 };
16448
16449 static const uint16 sq4CdPatchSocksSequelPoliceHandsOff[] = {
16450 0x38, PATCH_SELECTOR16(setMotion), // pushi setMotion
16451 0x78, // push1
16452 0x76, // push0
16453 0x76, // push0 [ y ]
16454 0x76, // push0
16455 0x81, 0x00, // lag 00
16456 0x4a, 0x0a, // send 0a [ ego setMotion: 0 y?, saves 4 bytes ]
16457 0x36, // push
16458 0x35, 0x20, // ldi 20
16459 0x04, // sub
16460 0xa3, 0x00, // sal 00 [ local0 = ego:y ]
16461 0x76, // push0
16462 0x45, 0x02, 0x00, // callb proc0_2 00 [ handsOff ]
16463 PATCH_END
16464 };
16465
16466 // The door to Sock's is immediately disposed of in the CD version, breaking its
16467 // Look message and preventing it from being drawn when restoring a saved game.
16468 // We remove the incorrect dispose call along with a redundant addToPic.
16469 //
16470 // Applies to: English PC CD
16471 // Responsible method: rm370:init
16472 // Fixes bug #10914
16473 static const uint16 sq4CdSignatureSocksDoor[] = {
16474 0x38, SIG_SELECTOR16(addToPic), // pushi addToPic
16475 0x76, // push0
16476 0x39, SIG_SELECTOR8(dispose), // pushi dispose
16477 0x76, // push0
16478 SIG_MAGICDWORD,
16479 0x72, SIG_UINT16(0x0176), // lofsa door
16480 0x4a, 0x08, // send 08 [ door addToPic: dispose: ]
16481 SIG_END
16482 };
16483
16484 static const uint16 sq4CdPatchSocksDoor[] = {
16485 0x32, PATCH_UINT16(0x0009), // jmp 0009
16486 PATCH_END
16487 };
16488
16489 // The scripts in SQ4CD support simultaneous playing of speech and subtitles,
16490 // but this was not available as an option. The following two patches enable
16491 // this functionality in the game's GUI options dialog.
16492 //
16493 // Patch 1: iconTextSwitch::show, called when the text options button is shown.
16494 // This is patched to add the "Both" text resource (i.e. we end up with
16495 // "Speech", "Text" and "Both")
16496 static const uint16 sq4CdSignatureTextOptionsButton[] = {
16497 SIG_MAGICDWORD,
16498 0x35, 0x01, // ldi 0x01
16499 0xa1, 0x53, // sag global[0x53]
16500 0x39, 0x03, // pushi 0x03
16501 0x78, // push1
16502 0x39, 0x09, // pushi 0x09
16503 0x54, 0x06, // self 0x06
16504 SIG_END
16505 };
16506
16507 static const uint16 sq4CdPatchTextOptionsButton[] = {
16508 PATCH_ADDTOOFFSET(+7),
16509 0x39, 0x0b, // pushi 0x0b
16510 PATCH_END
16511 };
16512
16513 // Patch 2: Adjust a check in babbleIcon::init (the two guys from Andromeda),
16514 // shown when dying/quitting.
16515 //
16516 // Responsible method: babbleIcon::init
16517 // Fixes bug: #6068
16518 static const uint16 sq4CdSignatureBabbleIcon[] = {
16519 SIG_MAGICDWORD,
16520 0x89, 0x5a, // lsg global[5a]
16521 0x35, 0x02, // ldi 02
16522 0x1a, // eq?
16523 0x31, 0x26, // bnt 26 [02a7]
16524 SIG_END
16525 };
16526
16527 static const uint16 sq4CdPatchBabbleIcon[] = {
16528 0x89, 0x5a, // lsg global[5a]
16529 0x35, 0x01, // ldi 01
16530 0x1a, // eq?
16531 0x2f, 0x26, // bt 26 [02a7]
16532 PATCH_END
16533 };
16534
16535 // Patch 3: Add the ability to toggle among the three available options
16536 // when the text options button is clicked: "Speech", "Text" and "Both".
16537 // Refer to the patch above for additional details.
16538 //
16539 // Responsible method: iconTextSwitch::doit
16540 static const uint16 sq4CdSignatureTextOptions[] = {
16541 SIG_MAGICDWORD,
16542 0x89, 0x5a, // lsg global[90]
16543 0x3c, // dup
16544 0x35, 0x01, // ldi 0x01
16545 0x1a, // eq?
16546 0x31, 0x06, // bnt 0x06 (0x0691)
16547 0x35, 0x02, // ldi 0x02
16548 0xa1, 0x5a, // sag global[90]
16549 0x33, 0x0a, // jmp 0x0a (0x69b)
16550 0x3c, // dup
16551 0x35, 0x02, // ldi 0x02
16552 0x1a, // eq?
16553 0x31, 0x04, // bnt 0x04 (0x069b)
16554 0x35, 0x01, // ldi 0x01
16555 0xa1, 0x5a, // sag global[90]
16556 0x3a, // toss
16557 0x38, SIG_SELECTOR16(show), // pushi show (0x00d9)
16558 0x76, // push0
16559 0x54, 0x04, // self 0x04
16560 0x48, // ret
16561 SIG_END
16562 };
16563
16564 static const uint16 sq4CdPatchTextOptions[] = {
16565 0x89, 0x5a, // lsg global[90]
16566 0x3c, // dup
16567 0x35, 0x03, // ldi 0x03 (acc = 3)
16568 0x1a, // eq? (global[90] == 3)
16569 0x2f, 0x07, // bt 0x07
16570 0x89, 0x5a, // lsg global[90]
16571 0x35, 0x01, // ldi 0x01 (acc = 1)
16572 0x02, // add (acc = global[90] + 1)
16573 0x33, 0x02, // jmp 0x02
16574 0x35, 0x01, // ldi 0x01 (reset acc to 1)
16575 0xa1, 0x5a, // sag global[90]
16576 0x33, 0x03, // jmp 0x03 (jump over the wasted bytes below)
16577 0x34, PATCH_UINT16(0x0000), // ldi 0x0000 (waste 3 bytes)
16578 0x3a, // toss
16579 // (the rest of the code is the same)
16580 PATCH_END
16581 };
16582
16583 // Vohaul's scene on the PocketPal (room 545) is incompatible with our dual
16584 // text+speech mode and reportedly has MIDI timing issues in text mode.
16585 //
16586 // This is an unusual scene in that it uses three empty MIDI songs to control
16587 // its text delays, which is probably why Sierra didn't fully upgrade it to
16588 // use a Narrator in the CD version. Instead the tVOHAUL Narrator is only used
16589 // in speech mode without the formatting it would need to display text. In text
16590 // mode the original floppy code handles that.
16591 //
16592 // We heavily patch this script to support text+speech mode and remove the MIDIs
16593 // from the equation. The trick to using tVOHAUL in dual mode is to set its
16594 // nMsgType to 2. This causes Sq4Narrator to change the game's message mode to
16595 // speech while it says a message, preventing it from displaying text in a
16596 // message box since it wasn't provided formatting. Our code needs to know the
16597 // real message mode while tVOHAUL is speaking so we store that in the script's
16598 // register for later use.
16599 //
16600 // The audio and text for Vohaul's messages aren't the same. The audio for the
16601 // second message also contains the third, and the third has no audio resource
16602 // at all. We work around this by setting a three second timer and displaying
16603 // the third text while the audio is already playing.
16604 //
16605 // Applies to: English PC CD
16606 // Responsible method: vohaulScript:changeState
16607 // Fixes bug #10241
16608 static const uint16 sq4CdSignatureVohaulPocketPalTextSpeech[] = {
16609 0x3c, // dup
16610 0x35, 0x00, // ldi 00
16611 0x1a, // eq?
16612 0x31, 0x26, // bnt 26 [ state 1 ]
16613 SIG_ADDTOOFFSET(+49),
16614 0x1a, // eq? [ is speech mode? ]
16615 0x31, 0x22, // bnt 22
16616 SIG_ADDTOOFFSET(+13),
16617 0x38, SIG_SELECTOR16(modNum), // pushi modNum [ unnecessary when modNum is room number ]
16618 0x78, // push1
16619 0x38, SIG_UINT16(0x0221), // pushi 0221
16620 SIG_ADDTOOFFSET(11),
16621 0x32, SIG_UINT16(0x00d1), // jmp 00d1 [ end of method ]
16622 0x38, SIG_SELECTOR16(setCycle), // pushi setCycle
16623 SIG_ADDTOOFFSET(+9),
16624 0x38, SIG_SELECTOR16(setCycle), // pushi setCycle
16625 0x78, // push1
16626 0x51, 0x59, // class RandCycle
16627 0x36, // push
16628 0x72, SIG_UINT16(0x0578), // lofsa vohaulEyes
16629 0x4a, 0x06, // send 06
16630 0x39, 0x04, // pushi 04
16631 SIG_MAGICDWORD,
16632 0x76, // push0
16633 0x72, SIG_UINT16(0x0740), // lofsa "Take a good look, Roger:"
16634 0x36, // push
16635 0x38, SIG_UINT16(0x0353), // pushi 0353
16636 0x7c, // pushSelf
16637 0x40, SIG_UINT16(0xf93e), 0x08, // call localproc_0062 08 [ display text until midi 851 finishes ]
16638 0x32, SIG_UINT16(0x00a7), // jmp 00a7 [ end of method ]
16639 SIG_ADDTOOFFSET(+10),
16640 0x1a, // eq? [ is text mode? ]
16641 SIG_ADDTOOFFSET(+52),
16642 0x1a, // eq? [ is speech mode? ]
16643 0x31, 0x0e, // bnt 0e
16644 SIG_ADDTOOFFSET(+11),
16645 0x32, SIG_UINT16(0x0057), // jmp 0057 [ end of method ]
16646 0x39, 0x04, // pushi 04
16647 0x76, // push0
16648 0x72, SIG_UINT16(0x0760), // lofsa "Remember this poor wretched soul..."
16649 0x36, // push
16650 0x38, SIG_UINT16(0x0354), // pushi 0354
16651 0x7c, // pushSelf
16652 0x40, SIG_UINT16(0xf8dc), 0x08, // call localproc_0062 08 [ display text until midi 852 finishes ]
16653 0x32, SIG_UINT16(0x0045), // jmp 0045 [ end of method ]
16654 SIG_ADDTOOFFSET(+6),
16655 0x89, 0x5a, // lsg 5a
16656 0x35, 0x01, // ldi 01
16657 0x1a, // eq? [ is text mode? ]
16658 0x31, 0x17, // bnt 17 [ skip text, set cycles = 1 ]
16659 0x78, // push1
16660 0x8b, 0x00, // lsl 00
16661 0x45, 0x0c, 0x02, // call proc0_12 02 [ unnecessary, localproc_0062 calls this ]
16662 0x39, 0x04, // pushi 04
16663 0x76, // push0
16664 0x72, SIG_UINT16(0x0784), // lofsa "...for he is your SON!"
16665 0x36, // push
16666 0x38, SIG_UINT16(0x0355), // pushi 0355
16667 0x7c, // pushSelf
16668 0x40, SIG_UINT16(0xf8b7), 0x08, // call localproc_0062 08 [ display text until midi 853 finishes ]
16669 0x33, 0x21, // jmp 21 [ end of method ]
16670 0x35, 0x01, // ldi 01
16671 0x65, 0x1a, // aTop cycles [ cycles = 1, speech completed or dismissed by user ]
16672 0x33, 0x1b, // jmp 1b [ end of method ]
16673 SIG_END
16674 };
16675
16676 static const uint16 sq4CdPatchVohaulPocketPalTextSpeech[] = {
16677 0x2f, 0x2a, // bt 26 [ state 1 ]
16678 0x81, 0x5a, // lag 5a
16679 0x65, 0x24, // aTop register [ register = message mode ]
16680 PATCH_ADDTOOFFSET(+49),
16681 0x12, // and [ is speech or dual mode? ]
16682 0x31, 0x21, // bnt 21
16683 PATCH_ADDTOOFFSET(+13),
16684 0x38, PATCH_SELECTOR16(nMsgType), // pushi nMsgType
16685 0x78, // push1
16686 0x38, PATCH_UINT16(0x0002), // pushi 0002 [ speech ]
16687 PATCH_ADDTOOFFSET(+11),
16688 0x33, 0x1a, // jmp 1a
16689 0x38, PATCH_SELECTOR16(setCycle), // pushi setCycle
16690 0x3c, // dup [ save 2 bytes ]
16691 PATCH_ADDTOOFFSET(+9),
16692 0x78, // push1
16693 0x51, 0x59, // class RandCycle
16694 0x36, // push
16695 0x72, PATCH_UINT16(0x0578), // lofsa vohaulEyes
16696 0x4a, 0x06, // send 06
16697 0x35, 0x02, // ldi 02
16698 0x65, 0x1c, // aTop seconds [ 2 second delay in text mode ]
16699 0x63, 0x24, // pToa register
16700 0x78, // push1
16701 0x12, // and [ is text or dual mode? ]
16702 0x31, 0x0b, // bnt 0b [ don't display text ]
16703 0x39, 0x03, // pushi 03
16704 0x76, // push0
16705 0x74, PATCH_UINT16(0x0740), // lofss "Take a good look, Roger:"
16706 0x76, // push0
16707 0x40, PATCH_UINT16(0xf93b), 0x06, // call localproc_0062 06 [ display text without midi ]
16708 PATCH_ADDTOOFFSET(+10),
16709 0x12, // and [ is text or dual mode? ]
16710 PATCH_ADDTOOFFSET(+52),
16711 0x12, // and [ is speech or dual mode? ]
16712 0x31, 0x0b, // bnt 0b
16713 PATCH_ADDTOOFFSET(+11),
16714 0x63, 0x24, // pToa register
16715 0x78, // push1
16716 0x12, // and [ is text or dual mode? ]
16717 0x31, 0x0f, // bnt 0f [ don't display text ]
16718 0x39, 0x03, // pushi 03
16719 0x76, // push0
16720 0x74, PATCH_UINT16(0x0760), // lofss "Remember this poor wretched soul..."
16721 0x76, // push0
16722 0x40, PATCH_UINT16(0xf8dd), 0x06, // call localproc_0062 06 [ display text without midi ]
16723 0x35, 0x03, // ldi 03
16724 0x65, 0x1c, // aTop seconds [ 3 second delay in text or dual mode ]
16725 PATCH_ADDTOOFFSET(+6),
16726 0x63, 0x1c, // pToa seconds
16727 0x2f, 0x1d, // bnt 1d [ speech dismissed by user, set cycles = 1 ]
16728 0x63, 0x24, // pToa register
16729 0x78, // push1
16730 0x1a, // eq? [ is text mode? ]
16731 0x31, 0x04, // bnt 04 [ don't set text delay ]
16732 0x35, 0x03, // ldi 03
16733 0x65, 0x1c, // aTop seconds [ 3 second delay in text mode ]
16734 0x63, 0x24, // pToa register
16735 0x78, // push1
16736 0x12, // and [ is text or dual mode? ]
16737 0x31, 0x0d, // bnt 0d [ skip text, set cycles = 1 ]
16738 0x39, 0x03, // pushi 03
16739 0x76, // push0
16740 0x74, PATCH_UINT16(0x0784), // lofss "...for he is your SON!"
16741 0x76, // push0
16742 0x40, PATCH_UINT16(0xf8b4), 0x06, // call localproc_0062 06 [ display text without midi ]
16743 0x33, 0x03, // jmp 03
16744 0x78, // push1
16745 0x69, 0x1a, // sTop cycles [ cycles = 1, speech completed ]
16746 PATCH_END
16747 };
16748
16749 // Walking around the sewer tunnels in the following sequence locks up the game:
16750 //
16751 // 1. Enter the ladder room (90) from the center room (95) while the slime is
16752 // just below the middle of the screen
16753 // 2. Enter the southwest room (105) from the ladder room (90)
16754 //
16755 // The script enterNorth has a code path which fails to advance the state and so
16756 // it gets stuck in handsOff mode. If sewer:status is 3, meaning the slime is
16757 // moving north or south, then enterNorth assumes that sewer:location, the room
16758 // the slime is in, must be room 105 or 90, but in the sequence above it is 95.
16759 //
16760 // We fix this by setting enterNorth:state to 1 in the problematic code path so
16761 // that the script advances. Sierra fixed this bug after the English PC floppy
16762 // versions but forgot to include the fix in the CD version over a year later.
16763 //
16764 // Applies to: English PC Floppy, English PC CD
16765 // Responsible method: enterNorth:changeState(0)
16766 // Fixes bug #10970
16767 static const uint16 sq4FloppySignatureSewerLockup[] = {
16768 SIG_MAGICDWORD,
16769 0x35, 0x01, // ldi 01
16770 0x65, 0x0a, // aTop state [ state = 1 ]
16771 0x32, SIG_UINT16(0x002e), // jmp 002e [ end of switch ]
16772 0x3c, // dup
16773 0x35, 0x5a, // ldi 5a [ ladder room ]
16774 0x1a, // eq?
16775 0x30, SIG_UINT16(0x0027), // bnt 0027 [ end of switch without setting state ]
16776 SIG_END
16777 };
16778
16779 static const uint16 sq4FloppyPatchSewerLockup[] = {
16780 PATCH_ADDTOOFFSET(+11),
16781 0x30, PATCH_UINT16(0xfff2), // bnt fff2 [ set state before end of switch ]
16782 PATCH_END
16783 };
16784
16785 static const uint16 sq4CDSignatureSewerLockup[] = {
16786 SIG_MAGICDWORD,
16787 0x35, 0x01, // ldi 01
16788 0x65, 0x14, // aTop state [ state = 1 ]
16789 0x33, 0x2c, // jmp 2c [ end of switch ]
16790 0x3c, // dup
16791 0x35, 0x5a, // ldi 5a [ ladder room ]
16792 0x1a, // eq?
16793 0x31, 0x26, // bnt 26 [ end of switch without setting state ]
16794 SIG_END
16795 };
16796
16797 static const uint16 sq4CDPatchSewerLockup[] = {
16798 PATCH_ADDTOOFFSET(+10),
16799 0x31, 0xf4, // bnt f4 [ set state before end of switch ]
16800 PATCH_END
16801 };
16802
16803 // SQ4CD had an easter egg room of things removed from Sierra games for legal
16804 // reasons, but the room itself was removed from the game. Instead the room's
16805 // pic (570) and messages (271) were left in along with the 18 digit timepod
16806 // code that attempts to load the missing room 271 and of course crashes.
16807 //
16808 // This wouldn't be a problem except that the code is publicly known due to NRS'
16809 // modified version of the game which includes a script 271 that recreates the
16810 // room. The code appears in easter egg lists, and players who don't realize it
16811 // only applies to a modified version attempt it and crash, so we disable it.
16812 //
16813 // Applies to: English PC CD
16814 // Responsible method: timeToTimeWarpS:changeState(1)
16815 // Fixes bug #11006
16816 static const uint16 sq4CdSignatureRemovedRoomTimepodCode[] = {
16817 SIG_MAGICDWORD,
16818 0x35, 0x01, // ldi 01 [ 1 == room 271 code was entered ]
16819 0xa3, 0x72, // sal 72
16820 SIG_END
16821 };
16822
16823 static const uint16 sq4CdPatchRemovedRoomTimepodCode[] = {
16824 0x35, 0x00, // ldi 00
16825 PATCH_END
16826 };
16827
16828 // Walking into Sock's dressing room (room 371) can cause ego to escape obstacle
16829 // boundaries and get stuck behind the wall or counter. Similar problems occur
16830 // in the original. The dressing room has no obstacle bounding the edge of the
16831 // screen. Instead, rm371:doit detects if ego has hit the edge and moves him
16832 // back to x coordinate 173 but doesn't change ego:y. If ego hits the edge on a
16833 // diagonal pathfinding move then this can place ego around the corner of one
16834 // of the obstacles that bound the top and bottom of the dressing room.
16835 // rm371:doit will then move ego within the obstacle and out of bounds.
16836 //
16837 // We fix this by extending the two obstacles an additional 10 pixels past the
16838 // edge of the screen so that ego can't get around their corners and get stuck.
16839 // Sierra reduced one of these coordinates in later floppy versions, and it's
16840 // not clear why, but this change wasn't included in the CD version.
16841 //
16842 // Applies to: All versions
16843 // Responsible method: rm371:init
16844 // Fixes bug #11055
16845 static const uint16 sq4SignatureSocksDressingRoomObstacles[] = {
16846 0x38, SIG_ADDTOOFFSET(+2), // pushi 321d or 319d [ x ]
16847 0x39, 0x46, // pushi 70d [ y ]
16848 SIG_ADDTOOFFSET(+125),
16849 0x38, SIG_MAGICDWORD, // pushi 321d [ x ]
16850 SIG_UINT16(0x0141),
16851 0x39, 0x49, // pushi 73d [ y ]
16852 SIG_END
16853 };
16854
16855 static const uint16 sq4PatchSocksDressingRoomObstacles[] = {
16856 0x38, PATCH_UINT16(0x014b), // pushi 331d [ x ]
16857 PATCH_ADDTOOFFSET(+127),
16858 0x38, PATCH_UINT16(0x014b), // pushi 331d [ x ]
16859 PATCH_END
16860 };
16861
16862 // SQ4CD lets you keep the unstable ordnance and its points for the entire game.
16863 //
16864 // The bomb in room 40 is a joke item with joke points which kills you when
16865 // entering the sewer, therefore you're not allowed to pick it up after leaving
16866 // the sewer. This was originally enforced in the floppy version by setting a
16867 // short timer which summons the Sequel Police to kill you. The shootEgo script
16868 // was refactored in the CD version and this code no longer works. Rather than
16869 // fix this, Sierra left the broken code in place, and added new code to kill
16870 // you when interacting with the tank if the previous room was the sewer. This
16871 // is incorrect since you can walk right to room 45 and return to room 40,
16872 // which defeats both checks and allows you to get and keep the bomb.
16873 //
16874 // We fix this by replacing the previous room test with a flag test. Flag 0 is
16875 // set when the police are on the streets and is what the original code tested.
16876 //
16877 // Applies to: English PC CD
16878 // Responsible method: tankScript:changeState(2)
16879 // Fixes bug #11077
16880 static const uint16 sq4CdSignatureUnstableOrdnance[] = {
16881 SIG_MAGICDWORD,
16882 0x31, 0x2b, // bnt 2b
16883 0x89, 0x0c, // lsg 0c [ previous room ]
16884 0x35, 0x48, // ldi 48 [ sewer manhole ]
16885 0x1a, // eq? [ came from sewer? ]
16886 SIG_END
16887 };
16888
16889 static const uint16 sq4CdPatchUnstableOrdnance[] = {
16890 PATCH_ADDTOOFFSET(+2),
16891 0x78, // push1
16892 0x76, // push0 [ flag 0, set when police are on streets ]
16893 0x45, 0x06, 0x02, // callb proc0_6 02 [ is flag 0 set? ]
16894 PATCH_END
16895 };
16896
16897 // script, description, signature patch
16898 static const SciScriptPatcherEntry sq4Signatures[] = {
16899 { true, 1, "Floppy: EGA intro delay fix", 2, sq4SignatureEgaIntroDelay, sq4PatchEgaIntroDelay },
16900 { true, 298, "Floppy: endless flight", 1, sq4FloppySignatureEndlessFlight, sq4FloppyPatchEndlessFlight },
16901 { true, 376, "Floppy: set sequel police description", 1, sq4FloppySignatureSequelPoliceDescription, sq4FloppyPatchSequelPoliceDescription },
16902 { true, 376, "Floppy: click atm card on sequel police fix", 1, sq4FloppySignatureClickAtmCardOnSequelPolice, sq4FloppyPatchClickAtmCardOnSequelPolice },
16903 { true, 376, "Floppy: throw stuff at sequel police fix", 1, sq4FloppySignatureThrowStuffAtSequelPolice, sq4FloppyPatchThrowStuffAtSequelPolice },
16904 { true, 700, "Floppy: throw stuff at sequel police fix", 1, sq4FloppySignatureThrowStuffAtSequelPolice, sq4FloppyPatchThrowStuffAtSequelPolice },
16905 { true, 40, "CD: unstable ordnance fix", 1, sq4CdSignatureUnstableOrdnance, sq4CdPatchUnstableOrdnance },
16906 { true, 45, "CD: walk in from below for room 45 fix", 1, sq4CdSignatureWalkInFromBelowRoom45, sq4CdPatchWalkInFromBelowRoom45 },
16907 { true, 105, "Floppy: sewer lockup fix", 1, sq4FloppySignatureSewerLockup, sq4FloppyPatchSewerLockup },
16908 { true, 105, "CD: sewer lockup fix", 1, sq4CDSignatureSewerLockup, sq4CDPatchSewerLockup },
16909 { true, 290, "CD: cedric easter egg fix", 1, sq4CdSignatureCedricEasterEgg, sq4CdPatchCedricEasterEgg },
16910 { true, 290, "CD: cedric lockup fix (1/2)", 1, sq4CdSignatureCedricLockup1, sq4CdPatchCedricLockup1 },
16911 { true, 290, "CD: cedric lockup fix (2/2)", 1, sq4CdSignatureCedricLockup2, sq4CdPatchCedricLockup2 },
16912 { true, 370, "CD: sock's sequel police hands-off fix", 1, sq4CdSignatureSocksSequelPoliceHandsOff, sq4CdPatchSocksSequelPoliceHandsOff },
16913 { true, 370, "CD: sock's door restore and message fix", 1, sq4CdSignatureSocksDoor, sq4CdPatchSocksDoor },
16914 { true, 370, "CD/Floppy: sock's sequel police flag fix (1/2)", 1, sq4SignatureSocksSequelPoliceFlag1, sq4PatchSocksSequelPoliceFlag1 },
16915 { true, 370, "CD/Floppy: sock's sequel police flag fix (2/2)", 1, sq4SignatureSocksSequelPoliceFlag2, sq4PatchSocksSequelPoliceFlag2 },
16916 { true, 371, "CD/Floppy: sock's dressing room obstacles fix", 1, sq4SignatureSocksDressingRoomObstacles, sq4PatchSocksDressingRoomObstacles },
16917 { false, 370, "Amiga: dress purchase flag check fix", 1, sq4AmigaSignatureDressPurchaseFlagCheck, sq4AmigaPatchDressPurchaseFlagCheck },
16918 { false, 371, "Amiga: dress purchase flag clear fix", 1, sq4AmigaSignatureDressPurchaseFlagClear, sq4AmigaPatchDressPurchaseFlagClear },
16919 { false, 386, "Amiga: dress purchase flag check fix", 1, sq4AmigaSignatureDressPurchaseFlagCheck, sq4AmigaPatchDressPurchaseFlagCheck },
16920 { true, 381, "CD: big and tall room description", 1, sq4CdSignatureBigAndTallDescription, sq4CdPatchBigAndTallDescription },
16921 { true, 385, "CD: monolith burger door message fix", 1, sq4CdSignatureMonolithBurgerDoor, sq4CdPatchMonolithBurgerDoor },
16922 { true, 390, "CD: hz so good sequel police lockup fix", 1, sq4CdSignatureHzSoGoodSequelPoliceLockup, sq4CdPatchHzSoGoodSequelPoliceLockup },
16923 { true, 390, "CD: hz so good sequel police cycler fix", 1, sq4CdSignatureHzSoGoodSequelPoliceCycler, sq4CdPatchHzSoGoodSequelPoliceCycler },
16924 { true, 391, "CD: missing Audio for universal remote control", 1, sq4CdSignatureMissingAudioUniversalRemote, sq4CdPatchMissingAudioUniversalRemote },
16925 { true, 396, "CD: get points for changing back clothes fix", 1, sq4CdSignatureGetPointsForChangingBackClothes, sq4CdPatchGetPointsForChangingBackClothes },
16926 { true, 405, "CD/Floppy: zero gravity blast fix", 1, sq4SignatureZeroGravityBlast, sq4PatchZeroGravityBlast },
16927 { true, 406, "CD/Floppy: zero gravity blast fix", 1, sq4SignatureZeroGravityBlast, sq4PatchZeroGravityBlast },
16928 { true, 410, "CD/Floppy: zero gravity blast fix", 1, sq4SignatureZeroGravityBlast, sq4PatchZeroGravityBlast },
16929 { true, 411, "CD/Floppy: zero gravity blast fix", 1, sq4SignatureZeroGravityBlast, sq4PatchZeroGravityBlast },
16930 { false, 531, "CD: disable timepod code for removed room", 1, sq4CdSignatureRemovedRoomTimepodCode, sq4CdPatchRemovedRoomTimepodCode },
16931 { true, 545, "CD: vohaul pocketpal text+speech fix", 1, sq4CdSignatureVohaulPocketPalTextSpeech, sq4CdPatchVohaulPocketPalTextSpeech },
16932 { true, 610, "CD: biker bar door fix", 1, sq4CdSignatureBikerBarDoor, sq4CdPatchBikerBarDoor },
16933 { true, 610, "CD: biker hands-on fix", 3, sq4CdSignatureBikerHandsOn, sq4CdPatchBikerHandsOn },
16934 { true, 611, "CD: biker hands-on fix", 1, sq4CdSignatureBikerHandsOn, sq4CdPatchBikerHandsOn },
16935 { true, 612, "CD: biker hands-on fix", 2, sq4CdSignatureBikerHandsOn, sq4CdPatchBikerHandsOn },
16936 { true, 613, "CD: biker hands-on fix", 2, sq4CdSignatureBikerHandsOn, sq4CdPatchBikerHandsOn },
16937 { true, 614, "CD: biker hands-on fix", 1, sq4CdSignatureBikerHandsOn, sq4CdPatchBikerHandsOn },
16938 { true, 613, "CD: biker timepod message fix", 1, sq4CdSignatureBikerTimepodMessage, sq4CdPatchBikerTimepodMessage },
16939 { true, 700, "CD: red shopper message fix", 1, sq4CdSignatureRedShopperMessageFix, sq4CdPatchRedShopperMessageFix },
16940 { true, 701, "CD: getting shot, while getting rope", 1, sq4CdSignatureGettingShotWhileGettingRope, sq4CdPatchGettingShotWhileGettingRope },
16941 { true, 706, "CD: biker crouch verb fix", 2, sq4CdSignatureBikerCrouchVerb, sq4CdPatchBikerCrouchVerb },
16942 { true, 0, "CD: Babble icon speech and subtitles fix", 1, sq4CdSignatureBabbleIcon, sq4CdPatchBabbleIcon },
16943 { true, 818, "CD: Speech and subtitles option", 1, sq4CdSignatureTextOptions, sq4CdPatchTextOptions },
16944 { true, 818, "CD: Speech and subtitles option button", 1, sq4CdSignatureTextOptionsButton, sq4CdPatchTextOptionsButton },
16945 SCI_SIGNATUREENTRY_TERMINATOR
16946 };
16947
16948 // ===========================================================================
16949 // When you leave Ulence Flats, another timepod is supposed to appear.
16950 // On fast machines, that timepod appears fully immediately and then
16951 // starts to appear like it should be. That first appearance is caused
16952 // by the scripts setting an invalid cel number and the machine being
16953 // so fast that there is no time for another script to actually fix
16954 // the cel number. On slower machines, the cel number gets fixed
16955 // by the cycler and that's why only fast machines are affected.
16956 // The same issue happens in Sierra SCI.
16957 // We simply set the correct starting cel number to fix the bug.
16958 // Responsible method: robotIntoShip::changeState(9)
16959 static const uint16 sq1vgaSignatureUlenceFlatsTimepodGfxGlitch[] = {
16960 0x39,
16961 SIG_MAGICDWORD, SIG_SELECTOR8(cel), // pushi cel
16962 0x78, // push1
16963 0x39, 0x0a, // pushi 0x0a (set ship::cel to 10)
16964 0x38, SIG_UINT16(0x00a0), // pushi 0x00a0 (ship::setLoop)
16965 SIG_END
16966 };
16967
16968 static const uint16 sq1vgaPatchUlenceFlatsTimepodGfxGlitch[] = {
16969 PATCH_ADDTOOFFSET(+3),
16970 0x39, 0x09, // pushi 0x09 (set ship::cel to 9)
16971 PATCH_END
16972 };
16973
16974 // In Ulence Flats, there is a space ship, that you will use at some point.
16975 // Near that space ship are 2 force field generators. When you look at the top
16976 // of those generators, the game will crash. This happens also in Sierra SCI.
16977 // It's caused by a jump, that goes out of bounds.
16978 //
16979 // We currently do not know if this was caused by a compiler glitch or if it
16980 // was a developer error. Anyway we patch this glitchy code, so that the game
16981 // won't crash anymore.
16982 //
16983 // Applies to at least: English Floppy
16984 // Responsible method: radar1::doVerb
16985 // Fixes bug: #6816
16986 static const uint16 sq1vgaSignatureUlenceFlatsGeneratorGlitch[] = {
16987 SIG_MAGICDWORD, 0x1a, // eq?
16988 0x30, SIG_UINT16(0xcdf4), // bnt [absolute 0xf000]
16989 SIG_END
16990 };
16991
16992 static const uint16 sq1vgaPatchUlenceFlatsGeneratorGlitch[] = {
16993 PATCH_ADDTOOFFSET(+1),
16994 0x32, PATCH_UINT16(0x0000), // jmp 0x0000 (waste bytes)
16995 PATCH_END
16996 };
16997
16998 // No documentation for this patch (TODO)
16999 static const uint16 sq1vgaSignatureEgoShowsCard[] = {
17000 SIG_MAGICDWORD,
17001 0x38, SIG_SELECTOR16(timesShownID), // pushi timesShownID
17002 0x78, // push1
17003 0x38, SIG_SELECTOR16(timesShownID), // pushi timesShownID
17004 0x76, // push0
17005 0x51, 0x7c, // class DeltaurRegion
17006 0x4a, 0x04, // send 0x04 (get timesShownID)
17007 0x36, // push
17008 0x35, 0x01, // ldi 1
17009 0x02, // add
17010 0x36, // push
17011 0x51, 0x7c, // class DeltaurRegion
17012 0x4a, 0x06, // send 0x06 (set timesShownID)
17013 0x36, // push (wrong, acc clobbered by class, above)
17014 0x35, 0x03, // ldi 0x03
17015 0x22, // lt?
17016 SIG_END
17017 };
17018
17019 // Note that this script patch is merely a reordering of the
17020 // instructions in the original script.
17021 static const uint16 sq1vgaPatchEgoShowsCard[] = {
17022 0x38, PATCH_SELECTOR16(timesShownID), // pushi timesShownID
17023 0x76, // push0
17024 0x51, 0x7c, // class DeltaurRegion
17025 0x4a, 0x04, // send 0x04 (get timesShownID)
17026 0x36, // push
17027 0x35, 0x01, // ldi 1
17028 0x02, // add
17029 0x36, // push (this push corresponds to the wrong one above)
17030 0x38, PATCH_SELECTOR16(timesShownID), // pushi timesShownID
17031 0x78, // push1
17032 0x36, // push
17033 0x51, 0x7c, // class DeltaurRegion
17034 0x4a, 0x06, // send 0x06 (set timesShownID)
17035 0x35, 0x03, // ldi 0x03
17036 0x22, // lt?
17037 PATCH_END
17038 };
17039
17040 // The spider droid on planet Korona has a fixed movement speed,
17041 // which is way faster than the default movement speed of ego.
17042 // This means that the player would have to turn up movement speed,
17043 // otherwise it will be impossible to escape it.
17044 // We fix this issue by making the droid move a bit slower than ego
17045 // does (relative to movement speed setting).
17046 //
17047 // Applies to at least: English PC floppy
17048 // Responsible method: spider::doit
17049 static const uint16 sq1vgaSignatureSpiderDroidTiming[] = {
17050 SIG_MAGICDWORD,
17051 0x63, 0x4e, // pToa script
17052 0x30, SIG_UINT16(0x0005), // bnt [further method code]
17053 0x35, 0x00, // ldi 00
17054 0x32, SIG_UINT16(0x0062), // jmp [super-call]
17055 0x38, SIG_UINT16(0x0088), // pushi 0088h (script)
17056 0x76, // push0
17057 0x81, 0x02, // lag global[2] (current room)
17058 0x4a, 0x04, // send 04 (get room script)
17059 0x30, SIG_UINT16(0x0005), // bnt [further method code]
17060 0x35, 0x00, // ldi 00
17061 0x32, SIG_UINT16(0x0052), // jmp [super-call]
17062 0x89, 0xa6, // lsg global[a6] (set to 1 when ego went up the skeleton tail, set to 2 when going down)
17063 0x35, 0x01, // ldi 01
17064 0x1a, // eq?
17065 0x30, SIG_UINT16(0x0012), // bnt [PChase set code] (when global[A6] != 1)
17066 0x81, 0xb5, // lag global[b5]
17067 0x30, SIG_UINT16(0x000d), // bnt [PChase set code] (when global[B5] == 0)
17068 0x38, SIG_UINT16(0x008c), // pushi 008c
17069 0x78, // push1
17070 0x72, SIG_UINT16(0x1cb6), // lofsa moveToPath
17071 0x36, // push
17072 0x54, 0x06, // self 06
17073 0x32, SIG_UINT16(0x0038), // jmp [super-call]
17074 // PChase set call
17075 0x81, 0xb5, // lag global[B5]
17076 0x18, // not
17077 0x30, SIG_UINT16(0x0032), // bnt [super-call] (when global[B5] != 0)
17078 // followed by:
17079 // is spider in current room
17080 // is global[A6h] == 2? -> set PChase
17081 SIG_END
17082 }; // 58 bytes)
17083
17084 // global[A6h] != 1 (did NOT went up the skeleton)
17085 // global[B5h] = 0 -> set PChase
17086 // global[B5h] != 0 -> do not do anything
17087 // global[A6h] = 1 (did went up the skeleton)
17088 // global[B5h] = 0 -> set PChase
17089 // global[B5h] != 0 -> set moveToPath
17090
17091 static const uint16 sq1vgaPatchSpiderDroidTiming[] = {
17092 0x63, 0x4e, // pToa script
17093 0x2f, 0x68, // bt [super-call]
17094 0x38, PATCH_UINT16(0x0088), // pushi 0088 (script)
17095 0x76, // push0
17096 0x81, 0x02, // lag global[2] (current room)
17097 0x4a, 0x04, // send 04
17098 0x2f, 0x5e, // bt [super-call]
17099 // --> 12 bytes saved
17100 // new code
17101 0x38, PATCH_UINT16(0x0176), // pushi 0176 (egoMoveSpeed)
17102 0x76, // push0
17103 0x81, 0x01, // lag global[1]
17104 0x4a, 0x04, // send 04 - sq1::egoMoveSpeed
17105 0x36, // push
17106 0x36, // push
17107 0x35, 0x03, // ldi 03
17108 0x0c, // shr
17109 0x02, // add --> egoMoveSpeed + (egoMoveSpeed >> 3)
17110 0x39, 0x01, // pushi 01 (waste 1 byte)
17111 0x02, // add --> egoMoveSpeed++
17112 0x65, 0x4c, // aTop cycleSpeed
17113 0x65, 0x5e, // aTop moveSpeed
17114 // new code end
17115 0x81, 0xb5, // lag global[B5]
17116 0x31, 0x13, // bnt [PChase code chunk]
17117 0x89, 0xa6, // lsg global[A6]
17118 0x35, 0x01, // ldi 01
17119 0x1a, // eq?
17120 0x31, 0x3e, // bnt [super-call]
17121 0x38, PATCH_UINT16(0x008c), // pushi 008c
17122 0x78, // push1
17123 0x72, PATCH_UINT16(0x1cb6), // lofsa moveToPath
17124 0x36, // push
17125 0x54, 0x06, // self 06 - spider::setScript(movePath)
17126 0x33, 0x32, // jmp [super-call]
17127 // --> 9 bytes saved
17128 PATCH_END
17129 };
17130
17131 // The Russian version of SQ1VGA has mangled class names in its scripts. This
17132 // isn't a problem in Sierra's interpreter since this is just metadata, but our
17133 // feature detection code looks up several classes by name and requires them to
17134 // exist. We fix this by patching the Motion, Rm, and Sound strings back to
17135 // their original values.
17136 //
17137 // Applies to: Russian PC Floppy
17138 // Fixes bug: #10156
17139 static const uint16 sq1vgaSignatureRussianMotionName[] = {
17140 SIG_MAGICDWORD,
17141 0x2A, 0x4D, 0x6F, 0x74, 0x69, // *Motion.
17142 0x6F, 0x6E, 0x20,
17143 SIG_END
17144 };
17145
17146 static const uint16 sq1vgaPatchRussianMotionName[] = {
17147 0x4D, 0x6F, 0x74, 0x69, 0x6F, // Motion
17148 0x6E, 0x00,
17149 PATCH_END
17150 };
17151 static const uint16 sq1vgaSignatureRussianRmName[] = {
17152 SIG_MAGICDWORD,
17153 0x2a, 0x52, 0x6d, 0x00, // *Rm
17154 SIG_END
17155 };
17156
17157 static const uint16 sq1vgaPatchRussianRmName[] = {
17158 0x52, 0x6d, 0x00, // Rm
17159 PATCH_END
17160 };
17161
17162 static const uint16 sq1vgaSignatureRussianSoundName[] = {
17163 SIG_MAGICDWORD,
17164 0x87, 0xa2, 0xe3, 0xaa, 0x00, 0x00, // ....
17165 SIG_END
17166 };
17167
17168 static const uint16 sq1vgaPatchRussianSoundName[] = {
17169 0x53, 0x6f, 0x75, 0x63, 0x64, // Sound
17170 PATCH_END
17171 };
17172
17173 // script, description, signature patch
17174 static const SciScriptPatcherEntry sq1vgaSignatures[] = {
17175 { true, 45, "Ulence Flats: timepod graphic glitch", 1, sq1vgaSignatureUlenceFlatsTimepodGfxGlitch, sq1vgaPatchUlenceFlatsTimepodGfxGlitch },
17176 { true, 45, "Ulence Flats: force field generator glitch", 1, sq1vgaSignatureUlenceFlatsGeneratorGlitch, sq1vgaPatchUlenceFlatsGeneratorGlitch },
17177 { true, 58, "Sarien armory droid zapping ego first time", 1, sq1vgaSignatureEgoShowsCard, sq1vgaPatchEgoShowsCard },
17178 { true, 704, "spider droid timing issue", 1, sq1vgaSignatureSpiderDroidTiming, sq1vgaPatchSpiderDroidTiming },
17179 { true, 989, "rename russian Sound class", 1, sq1vgaSignatureRussianSoundName, sq1vgaPatchRussianSoundName },
17180 { true, 992, "rename russian Motion class", 1, sq1vgaSignatureRussianMotionName, sq1vgaPatchRussianMotionName },
17181 { true, 994, "rename russian Rm class", 1, sq1vgaSignatureRussianRmName, sq1vgaPatchRussianRmName },
17182 SCI_SIGNATUREENTRY_TERMINATOR
17183 };
17184
17185 // ===========================================================================
17186 // The toolbox in sq5 is buggy. When you click on the upper part of the "put
17187 // in inventory" button (some items only - for example the hole puncher at the
17188 // upper left), points will get awarded correctly, and the item will get put
17189 // into the player's inventory, but you will then get a "not here" message,
17190 // and the item will also remain as the current mouse cursor.
17191 // The bug report says items may get lost when exiting the toolbox screen,
17192 // That was not reproduced.
17193 // This is caused by the mouse-click event getting reprocessed (which wouldn't
17194 // be a problem by itself). Reprocessing treats coordinates differently from
17195 // the first click (script 226 includes a local subroutine, which checks
17196 // coordinates in a hardcoded way w/o port-adjustment).
17197 // Because of this, the hotspot for the button is lower than it should be,
17198 // which results in the game thinking the user didn't click on the button and
17199 // also results in the "not here" message.
17200 // We fix it by combining state 0 + 1 of takeTool::changeState and so stopping
17201 // the event from being reprocessed... without touching SCI system scripts.
17202 // Applies to at least: English/German/French PC floppy
17203 // Responsible method: takeTool::changeState
17204 // Fixes bug: #6457
17205 static const uint16 sq5SignatureToolboxFix[] = {
17206 0x31, 0x13, // bnt [check for state 1]
17207 SIG_MAGICDWORD,
17208 0x38, SIG_UINT16(0x00aa), // pushi 00aa
17209 0x39, 0x05, // pushi 05
17210 0x39, 0x16, // pushi 16
17211 0x76, // push0
17212 0x39, 0x03, // pushi 03
17213 0x76, // push0
17214 0x7c, // pushSelf
17215 0x81, 0x5b, // lag global[5b]
17216 0x4a, 0x0e, // send 0e
17217 0x32, SIG_UINT16(0x0088), // jmp [end of method]
17218 0x3c, // dup
17219 0x35, 0x01, // ldi 01
17220 0x1a, // eq?
17221 0x31, 0x28, // bnt [check for state 2]
17222 SIG_END
17223 };
17224
17225 static const uint16 sq5PatchToolboxFix[] = {
17226 0x31, 0x41, // bnt [check for state 2]
17227 PATCH_ADDTOOFFSET(+16), // skip to jmp offset
17228 0x35, 0x01, // ldi 01
17229 0x65, 0x14, // aTop [state]
17230 0x36, 0x00, 0x00, // ldi 0000 (waste 3 bytes)
17231 0x35, 0x00, // ldi 00 (waste 2 bytes)
17232 PATCH_END
17233 };
17234
17235 // WORKAROUND: Script needed, because of differences in our pathfinding
17236 // algorithm
17237 // After entering the drive bay (room 1000) through the hallway, clicking walk
17238 // in most places causes ego to automatically turn around and return to the
17239 // previous room. This is due to differences in our pathfinding algorithm from
17240 // Sierra's which results in ego first walking backwards into the control area
17241 // that triggers the script sExitToHall.
17242 //
17243 // We work around this by adjusting ego's initial MoveTo position by a few
17244 // pixels to one which doesn't cause pathfinding to send ego backwards.
17245 //
17246 // Applies to: PC Floppy
17247 // Responsible method: sEnterFromHall:changeState(0)
17248 // Fixes bug: #7155
17249 static const uint16 sq5SignatureDriveBayPathfindingFix[] = {
17250 SIG_MAGICDWORD,
17251 0x39, 0x0e, // pushi 0e [ x = 14d ]
17252 0x39, 0x6e, // pushi 6e [ y = 110d ]
17253 SIG_END
17254 };
17255
17256 static const uint16 sq5PatchDriveBayPathfindingFix[] = {
17257 0x39, 0x10, // pushi 10 [ x = 16d ]
17258 0x39, 0x6f, // pushi 6f [ y = 111d ]
17259 PATCH_END
17260 };
17261
17262 // Sitting in the captain's chair while Droole plays paddle ball randomly locks
17263 // up the game. Upon sitting, sTakeCommand plays a sound using theMusic3 and
17264 // waits for it to complete. This is the same object that's used to play the
17265 // paddle ball sound. If ego sits before a paddle ball sound starts or Droole
17266 // stops paddling and disposes the sound then sTakeCommand is never cued.
17267 //
17268 // We fix this conflict by using a different Sound object for the chair.
17269 // theMusic4 is only used once while meeting the crew in sNewCaptain.
17270 //
17271 // Applies to: All versions
17272 // Responsible method: sTakeCommand:changeState
17273 // Fixes bug: #6130
17274 static const uint16 sq5SignatureCaptainChairFix[] = {
17275 SIG_MAGICDWORD,
17276 0x76, // push0
17277 0x72, SIG_UINT16(0x0018), // lofsa theMusic4
17278 SIG_ADDTOOFFSET(+947),
17279 0x72, SIG_UINT16(0x02ec), // lofsa theMusic3
17280 SIG_ADDTOOFFSET(+26),
17281 0x72, SIG_UINT16(0x02ec), // lofsa theMusic3
17282 SIG_END
17283 };
17284
17285 static const uint16 sq5PatchCaptainChairFix[] = {
17286 PATCH_ADDTOOFFSET(+951),
17287 0x72, PATCH_UINT16(0x0018), // lofsa theMusic4
17288 PATCH_ADDTOOFFSET(+26),
17289 0x72, PATCH_UINT16(0x0018), // lofsa theMusic4
17290 PATCH_END
17291 };
17292
17293 // When using the fruit on WD40 in room 305, she can take off before ego's
17294 // animation completes and lock up the game. WD40 remains on the log for five
17295 // seconds but ego's animation runs at the game speed setting and the scripts
17296 // don't coordinate. At slow speeds, ego's animation can take all five seconds.
17297 //
17298 // We fix this by not allowing WD40 to take off while ego has a script running.
17299 // To do this we use existing code in sWD40LandOverRog that retries the current
17300 // state after 10 ticks. This preserves the scene's timing unless WD40 would
17301 // have gotten stuck, in which case she now waits for sFruitUpWD40 to complete.
17302 //
17303 // Applies to: All versions
17304 // Responsible method: sWD40LandOverRog:changeState
17305 // Fixes bug: #5162
17306 static const uint16 sq5SignatureWd40FruitFix[] = {
17307 0x6d, 0x14, // dpToa state [ state-- ]
17308 0x35, 0x0a, // ldi 0a
17309 SIG_MAGICDWORD,
17310 0x65, 0x20, // aTop ticks [ ticks = 10 ]
17311 0x32, SIG_UINT16(0x0106), // jmp 0106 [ end of method ]
17312 SIG_ADDTOOFFSET(+57),
17313 0x31, 0x28, // bnt 28 [ state 5 ]
17314 SIG_ADDTOOFFSET(+7),
17315 0x31, 0x18, // bnt 18
17316 0x78, // push1
17317 SIG_ADDTOOFFSET(+20),
17318 0x32, SIG_UINT16(0x00aa), // jmp 00aa [ end of method ]
17319 0x35, 0x01, // ldi 01
17320 0x65, 0x1a, // aTop cycles
17321 0x32, SIG_UINT16(0x00a3), // jmp 00a3 [ end of method ]
17322 0x3c, // dup
17323 0x35, 0x05, // ldi 05
17324 0x1a, // eq?
17325 0x31, 0x11, // bnt 11 [ state 6 ]
17326 0x39, SIG_SELECTOR8(play), // pushi play
17327 0x78, // push1
17328 0x39, 0x4b, // pushi 4b
17329 0x72, SIG_UINT16(0x096e), // lofsa theMusic3
17330 0x4a, 0x06, // send 06 [ theMusic3 play: 75 ]
17331 0x35, 0x05, // ldi 05
17332 0x65, 0x1c, // aTop seconds [ seconds = 5 ]
17333 0x32, SIG_UINT16(0x008c), // jmp 008c [ end of method ]
17334 0x3c, // dup
17335 0x35, 0x06, // ldi 06
17336 0x1a, // eq?
17337 0x30, SIG_UINT16(0x0053), // bnt 0053 [ state 7 ]
17338 SIG_END
17339 };
17340
17341 static const uint16 sq5PatchWd40FruitFix[] = {
17342 PATCH_ADDTOOFFSET(+66),
17343 0x31, 0x22, // bnt 22 [ state 5 ]
17344 PATCH_ADDTOOFFSET(+7),
17345 0x78, // push1
17346 0x31, 0x16, // bnt 16
17347 PATCH_ADDTOOFFSET(+20),
17348 0x3a, // toss
17349 0x48, // ret
17350 0x69, 0x1a, // sTop cycles [ cycles = 1 ]
17351 0x3c, // dup
17352 0x35, 0x05, // ldi 05
17353 0x1a, // eq?
17354 0x31, 0x0d, // bnt 0d [ state 6 ]
17355 0x39, PATCH_SELECTOR8(play), // pushi play
17356 0x78, // push1
17357 0x39, 0x4b, // pushi 4b
17358 0x72, PATCH_UINT16(0x096e), // lofsa theMusic3
17359 0x4a, 0x06, // send 06 [ theMusic3 play: 75 ]
17360 0x69, 0x1c, // sTop seconds [ seconds = 5 ]
17361 0x48, // ret
17362 0x3c, // dup
17363 0x35, 0x06, // ldi 06
17364 0x1a, // eq?
17365 0x31, 0x5e, // bnt 5e [ state 7 ]
17366 0X38, PATCH_SELECTOR16(script), // pushi script
17367 0x76, // push0
17368 0x81, 0x00, // lag 00
17369 0x4a, 0x04, // send 04 [ ego script? ]
17370 0x2e, PATCH_UINT16(0xff76), // bt ff76 [ state--, ticks = 10 ]
17371 PATCH_END
17372 };
17373
17374 // In the first release of SQ5, when the cloaking device alarm countdown on
17375 // WD40's ship expires, a script enters an infinite loop and the interpreter
17376 // stops responding.
17377 //
17378 // We fix this as Sierra did in later versions by adding a call to SQ5:handsOn
17379 // and removing the call to sCountDown:dispose before going to deathRoom.
17380 //
17381 // Applies to: English PC 1.03
17382 // Responsible method: sCountDown:changeState(3)
17383 // Fixes bug: #11255
17384 static const uint16 sq5SignatureWd40AlarmCountdownFix[] = {
17385 0x3c, // dup
17386 0x35, 0x03, // ldi 03
17387 0x1a, // eq?
17388 0x31, 0x0b, // bnt 0b [ end of method ]
17389 0x78, // push1
17390 0x39, 0x15, // pushi 15
17391 SIG_MAGICDWORD,
17392 0x45, 0x09, 0x02, // callb proc0_9 02 [ go to deathRoom ]
17393 0x39, SIG_SELECTOR8(dispose), // pushi dispose
17394 0x76, // push0
17395 0x54, 0x04, // self 04 [ self dispose: ]
17396 SIG_END
17397 };
17398
17399 static const uint16 sq5PatchWd40AlarmCountdownFix[] = {
17400 0x38, PATCH_SELECTOR16(handsOn),// pushi handsOn
17401 0x76, // push0
17402 0x81, 0x01, // lag 01
17403 0x4a, 0x04, // send 04 [ SQ5 handsOn: ]
17404 0x78, // push1
17405 0x39, 0x15, // pushi 15
17406 0x45, 0x09, 0x02, // callb proc0_9 02 [ go to deathRoom ]
17407 0x3a, // toss
17408 0x48, // ret
17409 PATCH_END
17410 };
17411
17412 // In the transporter room, several scripts attempt to temporarily set ego's
17413 // speed to 6 but instead change the game speed. This prevents ego's speed from
17414 // being restored. The user must then do this manually in the control panel.
17415 // These bugs are due to calling ego:setSpeed instead of ego:cycleSpeed, which
17416 // we fix. This occurs when randomly beaming in with the funnyBeam script and
17417 // when talking to Cliffy about Bea before curing her.
17418 //
17419 // Applies to: All versions
17420 // Responsible methods: funnyBeam:changeState, talkAboutBea:changeState
17421 // Fixes bug: #11264
17422 static const uint16 sq5SignatureTransporterRoomSpeedFix[] = {
17423 0x38, SIG_MAGICDWORD, // pushi setSpeed
17424 SIG_SELECTOR16(setSpeed),
17425 0x78, // push1
17426 0x39, 0x06, // pushi 06
17427 SIG_END
17428 };
17429
17430 static const uint16 sq5PatchTransporterRoomSpeedFix[] = {
17431 0x38, PATCH_SELECTOR16(cycleSpeed), // pushi cycleSpeed
17432 PATCH_END
17433 };
17434
17435 // script, description, signature patch
17436 static const SciScriptPatcherEntry sq5Signatures[] = {
17437 { true, 200, "captain chair lockup fix", 1, sq5SignatureCaptainChairFix, sq5PatchCaptainChairFix },
17438 { true, 226, "toolbox fix", 1, sq5SignatureToolboxFix, sq5PatchToolboxFix },
17439 { true, 243, "transporter room speed fix", 3, sq5SignatureTransporterRoomSpeedFix, sq5PatchTransporterRoomSpeedFix },
17440 { true, 305, "wd40 fruit fix", 1, sq5SignatureWd40FruitFix, sq5PatchWd40FruitFix },
17441 { true, 335, "wd40 alarm countdown fix", 1, sq5SignatureWd40AlarmCountdownFix, sq5PatchWd40AlarmCountdownFix },
17442 { true, 1000, "drive bay pathfinding fix", 1, sq5SignatureDriveBayPathfindingFix, sq5PatchDriveBayPathfindingFix },
17443 SCI_SIGNATUREENTRY_TERMINATOR
17444 };
17445
17446 #ifdef ENABLE_SCI32
17447 #pragma mark -
17448 #pragma mark RAMA
17449
17450 // RAMA has custom video benchmarking code that needs to be disabled; see
17451 // sci2BenchmarkSignature
17452 static const uint16 ramaBenchmarkSignature[] = {
17453 0x38, SIG_SELECTOR16(view), // pushi view
17454 SIG_MAGICDWORD,
17455 0x78, // push1
17456 0x38, SIG_UINT16(0xfdd4), // pushi 64980
17457 SIG_END
17458 };
17459
17460 static const uint16 ramaBenchmarkPatch[] = {
17461 0x34, PATCH_UINT16(0x2710), // ldi 10000
17462 0x48, // ret
17463 PATCH_END
17464 };
17465
17466 // RAMA uses a custom save game format that game scripts read and write
17467 // manually. The save game format serialises object references, which SSCI could
17468 // be done just by writing int16s (since object references were just 16-bit
17469 // indexes), but in ScummVM we have to write the full 32-bit reg_t. We hijack
17470 // kFileIOReadWord/kFileIOWriteWord to do this for us, but we need the game to
17471 // agree to use those kFileIO calls instead of doing raw reads and creating its
17472 // own numbers, as it tries to do here in `SaveManager::readWord`.
17473 static const uint16 ramaSerializeRegTSignature1[] = {
17474 SIG_MAGICDWORD,
17475 0x38, SIG_SELECTOR16(newWith), // pushi newWith ($10b)
17476 0x7a, // push2
17477 0x7a, // push2
17478 0x72, SIG_UINT16(0x0000), // lofsa ""
17479 0x36, // push
17480 0x51, 0x0f, // class Str
17481 SIG_END
17482 };
17483
17484 static const uint16 ramaSerializeRegTPatch1[] = {
17485 0x38, PATCH_SELECTOR16(readWord), // pushi readWord
17486 0x76, // push0
17487 0x62, PATCH_SELECTOR16(saveFilePtr), // pToa saveFilePtr
17488 0x4a, PATCH_UINT16(0x0004), // send 4
17489 0x48, // ret
17490 PATCH_END
17491 };
17492
17493 // When restoring a NukeTimer client, the game makes a self-call to
17494 // `NukeTimer::getSubscriberObj` from `NukeTimer::serialize`, but forgets to
17495 // pass a required argument. In SSCI this happens to work because the value on
17496 // the stack where the first argument should be is the `getSubscriberObj`
17497 // selector, so it evaluates to true, but currently ScummVM defaults
17498 // uninitialised param reads to 0 so the game was following the wrong path and
17499 // breaking.
17500 // Applies to at least: US English
17501 // Fixes bug: #10263
17502 static const uint16 ramaNukeTimerSignature[] = {
17503 0x7e, SIG_ADDTOOFFSET(+2), // line whatever
17504 SIG_MAGICDWORD,
17505 0x38, SIG_SELECTOR16(getSubscriberObj), // pushi getSubscriberObj ($3ca)
17506 0x76, // push0
17507 0x54, SIG_UINT16(0x0004), // self 4
17508 SIG_END
17509 };
17510
17511 static const uint16 ramaNukeTimerPatch[] = {
17512 0x38, PATCH_SELECTOR16(getSubscriberObj), // pushi getSubscriberObj ($3ca)
17513 0x78, // push1
17514 0x38, PATCH_UINT16(0x0001), // pushi 1 (wasting bytes)
17515 0x54, PATCH_UINT16(0x0006), // self 6
17516 PATCH_END
17517 };
17518
17519 // When opening a datacube on the pocket computer, `DocReader::init` will try
17520 // to perform arithmetic on a pointer to `thighComputer::plane` then use the
17521 // resulting value as the priority for the DocReader. This happened to work in
17522 // SSCI because the plane pointer would just be a high numeric value, but
17523 // ScummVM needs an actual number, not a pointer.
17524 // Applies to at least: US English
17525 static const uint16 ramaDocReaderInitSignature[] = {
17526 0x39, SIG_SELECTOR8(priority), // pushi priority ($1a)
17527 0x78, // push1
17528 0x39, SIG_SELECTOR8(plane), // pushi plane ($19)
17529 0x76, // push0
17530 0x7a, // push2
17531 SIG_MAGICDWORD,
17532 0x39, 0x2c, // pushi 44
17533 0x76, // push0
17534 0x43, 0x02, SIG_UINT16(0x04), // callk ScriptID, 4
17535 SIG_END
17536 };
17537
17538 static const uint16 ramaDocReaderInitPatch[] = {
17539 PATCH_ADDTOOFFSET(+3), // pushi priority, push1
17540 0x39, PATCH_SELECTOR8(priority), // pushi priority
17541 PATCH_END
17542 };
17543
17544 // It is not possible to change the directory for ScummVM save games, so
17545 // disable the "change directory" button in the RAMA save dialog.
17546 static const uint16 ramaChangeDirSignature[] = {
17547 SIG_MAGICDWORD,
17548 0x7e, SIG_UINT16(0x0064), // line 100
17549 0x39, SIG_SELECTOR8(state), // pushi state ($1d)
17550 0x78, // push1
17551 0x39, 0x03, // pushi 3
17552 0x72, SIG_ADDTOOFFSET(+2), // lofsa changeDirI
17553 0x4a, SIG_UINT16(0x000e), // send 14
17554 SIG_END
17555 };
17556
17557 static const uint16 ramaChangeDirPatch[] = {
17558 PATCH_ADDTOOFFSET(+6), // line 100, pushi state, push1
17559 0x39, 0x00, // pushi 0
17560 PATCH_END
17561 };
17562
17563 static const SciScriptPatcherEntry ramaSignatures[] = {
17564 { true, 55, "fix bad DocReader::init priority calculation", 1, ramaDocReaderInitSignature, ramaDocReaderInitPatch },
17565 { true, 85, "fix SaveManager to use normal readWord calls", 1, ramaSerializeRegTSignature1, ramaSerializeRegTPatch1 },
17566 { true, 201, "fix crash restoring save games using NukeTimer", 1, ramaNukeTimerSignature, ramaNukeTimerPatch },
17567 { true, 64908, "disable video benchmarking", 1, ramaBenchmarkSignature, ramaBenchmarkPatch },
17568 { true, 64990, "disable change directory button", 1, ramaChangeDirSignature, ramaChangeDirPatch },
17569 SCI_SIGNATUREENTRY_TERMINATOR
17570 };
17571
17572 #pragma mark -
17573 #pragma mark Shivers
17574
17575 // In room 35170, there is a CCTV control station with a joystick that must be
17576 // clicked and dragged to pan the camera. In order to enable dragging, on
17577 // mousedown, the `vJoystick::handleEvent` method calls `vJoystick::doVerb(1)`,
17578 // which enables the drag functionality of the joystick. However,
17579 // `vJoystick::handleEvent` then makes a super call to
17580 // `ShiversProp::handleEvent`, which calls `vJoystick::doVerb()`. This second
17581 // call, which fails to pass an argument, causes an uninitialized read off the
17582 // stack for the first parameter. In SSCI, this happens to work because the
17583 // uninitialized value on the stack happens to be 1. Disabling the super call
17584 // avoids the bad doVerb call without any apparent ill effect.
17585 // The same problem exists when trying to drag the volume & brightness sliders
17586 // in the main menu. These controls are also fixed by this patch.
17587 // Applies to at least: US English
17588 static const uint16 shiversEventSuperCallSignature[] = {
17589 SIG_MAGICDWORD,
17590 0x38, SIG_SELECTOR16(handleEvent), // pushi handleEvent
17591 0x78, // push1
17592 0x8f, 0x01, // lsp param[1]
17593 0x59, 0x02, // &rest 2
17594 0x57, 0x7f, SIG_UINT16(0x0006), // super ShiversProp[7f], 6
17595 SIG_END
17596 };
17597
17598 static const uint16 shiversEventSuperCallPatch[] = {
17599 0x48, // ret
17600 PATCH_END
17601 };
17602
17603 // When the Ixupi is present in the Gods and Items intro room, the game tries to
17604 // play a sound using the play selector, but its arguments are only appropriate
17605 // for the fade selector.
17606 // If the badly constructed sound object from this call ends up receiving a
17607 // signal at any time in the future, the game will try to send to a number and
17608 // crash (because the third argument to play is supposed to be a client object,
17609 // but here it is a number instead). Other rooms make this same call with the
17610 // correct fade selector, so fix the selector here to match.
17611 // Applies to at least: English CD
17612 static const uint16 shiversGodsIxupiPlaySoundSignature[] = {
17613 SIG_MAGICDWORD,
17614 0x39, SIG_SELECTOR8(play), // pushi play ($33)
17615 0x38, SIG_UINT16(0x0006), // pushi 6
17616 SIG_END
17617 };
17618
17619 static const uint16 shiversGodsIxupiPlaySoundPatch[] = {
17620 0x38, PATCH_SELECTOR16(fade), // pushi fade ($f3)
17621 0x39, 0x06, // pushi 6
17622 PATCH_END
17623 };
17624
17625 // script, description, signature patch
17626 static const SciScriptPatcherEntry shiversSignatures[] = {
17627 { true, 990, "fix volume & brightness sliders", 2, shiversEventSuperCallSignature, shiversEventSuperCallPatch },
17628 { true, 23090, "fix bad Ixupi sound call", 1, shiversGodsIxupiPlaySoundSignature, shiversGodsIxupiPlaySoundPatch },
17629 { true, 35170, "fix CCTV joystick interaction", 1, shiversEventSuperCallSignature, shiversEventSuperCallPatch },
17630 { true, 64908, "disable video benchmarking", 1, sci2BenchmarkSignature, sci2BenchmarkPatch },
17631 SCI_SIGNATUREENTRY_TERMINATOR
17632 };
17633
17634 #pragma mark -
17635 #pragma mark Space Quest 6
17636
17637 // After the explosion in the Quarters of Deepship 86, the game tries to perform
17638 // a dramatic long fade, but does this with an unreasonably large number of
17639 // divisions which takes tens of seconds to finish (because transitions are not
17640 // CPU-dependent in ScummVM).
17641 // Fixes bug: #9590
17642 static const uint16 sq6SlowTransitionSignature1[] = {
17643 SIG_MAGICDWORD,
17644 0x38, SIG_UINT16(0x0578), // pushi $578
17645 0x51, 0x33, // class Styler
17646 SIG_END
17647 };
17648
17649 static const uint16 sq6SlowTransitionPatch1[] = {
17650 0x38, PATCH_UINT16(0x01f4), // pushi 500
17651 PATCH_END
17652 };
17653
17654 // For whatever reason, SQ6 sets the default number of transition divisions to
17655 // be a much larger value at startup (200 vs 30) if it thinks it is running in
17656 // Windows. Room 410 (eulogy room) also unconditionally resets divisions to the
17657 // larger value.
17658 // Fixes bug: #9590
17659 static const uint16 sq6SlowTransitionSignature2[] = {
17660 SIG_MAGICDWORD,
17661 0x38, SIG_UINT16(0x00c8), // pushi $c8
17662 0x51, 0x33, // class Styler
17663 SIG_END
17664 };
17665
17666 static const uint16 sq6SlowTransitionPatch2[] = {
17667 0x38, PATCH_UINT16(0x001e), // pushi 30
17668 PATCH_END
17669 };
17670
17671 // SQ6 has custom video benchmarking code that needs to be disabled; see
17672 // sci2BenchmarkSignature. (The sci2BenchmarkPatch is suitable for use with
17673 // SQ6 as well.)
17674 static const uint16 sq6BenchmarkSignature[] = {
17675 SIG_MAGICDWORD,
17676 0x38, SIG_SELECTOR16(init), // pushi init
17677 0x76, // push0
17678 0x7e, SIG_ADDTOOFFSET(+2), // line
17679 0x38, SIG_SELECTOR16(posn), // pushi posn
17680 SIG_END
17681 };
17682
17683 // SQ6 advertises a maximum score of 500 but the game is missing two points.
17684 // The "Official Player's Guide" authorized by Sierra includes a point list
17685 // that adds up to 500. It claims that three points should be awarded instead
17686 // of two when untangling the hose and attaching the staple and celery.
17687 //
17688 // Since an official point list exists that adds up to the 500 points that the
17689 // game advertises, we add the two missing points to the inventory actions.
17690 // Two versions of this patch are necessary because the English PC versions
17691 // were compiled with line number debugging instructions and the subsequent
17692 // French, German, and Mac versions weren't.
17693 //
17694 // Applies to: All versions
17695 // Responsible methods: Hookah_Hose:cue, Staple:doVerb, Celery:doVerb
17696 // Fixes bug: #11275
17697 static const uint16 sq6MissingPointsSignature[] = {
17698 0x7e, SIG_ADDTOOFFSET(+2), // line
17699 SIG_MAGICDWORD,
17700 0x39, SIG_SELECTOR8(points), // pushi points
17701 0x78, // push1
17702 0x7a, // push2
17703 SIG_END
17704 };
17705
17706 static const uint16 sq6MissingPointsPatch[] = {
17707 0x39, PATCH_SELECTOR8(points), // pushi points
17708 0x39, 0x01, // pushi 01
17709 0x38, PATCH_UINT16(0x0003), // pushi 0003
17710 PATCH_END
17711 };
17712
17713 // French, German, and Mac versions don't include line number instructions
17714 // and require specific patches.
17715 static const uint16 sq6StapleCeleryPointSignature[] = {
17716 0x72, SIG_ADDTOOFFSET(+2), // lofsa Grappling_Hook
17717 0x36, // push
17718 0x81, 0x09, // lag 09
17719 0x4a, SIG_UINT16(0x0006), // send 06
17720 0x39, SIG_SELECTOR8(points), // pushi points
17721 SIG_MAGICDWORD,
17722 0x78, // push1
17723 0x7a, // push2
17724 0x38, SIG_SELECTOR16(setCursor),// pushi setCursor
17725 SIG_END
17726 };
17727
17728 static const uint16 sq6StapleCeleryPointPatch[] = {
17729 0x74, PATCH_ADDTOOFFSET(+2), // lofss Grappling_Hook
17730 0x81, 0x09, // lag 09
17731 0x4a, PATCH_UINT16(0x0006), // send 06
17732 0x39, PATCH_SELECTOR8(points), // pushi points
17733 0x78, // push1
17734 0x39, 0x03, // pushi 03
17735 PATCH_END
17736 };
17737
17738 static const uint16 sq6HookahHosePointSignature[] = {
17739 0x30, SIG_UINT16(0x0054), // bnt 0054
17740 SIG_ADDTOOFFSET(+75),
17741 0x72, SIG_ADDTOOFFSET(+2), // lofsa Hookah_Connected
17742 0x36, // push
17743 0x81, 0x09, // lag 09
17744 0x4a, SIG_UINT16(0x000c), // send 0c
17745 0x39, SIG_SELECTOR8(points), // pushi points
17746 SIG_MAGICDWORD,
17747 0x78, // push1
17748 0x7a, // push2
17749 0x81, 0x01, // lag 01
17750 SIG_END
17751 };
17752
17753 static const uint16 sq6HookahHosePointPatch[] = {
17754 0x30, PATCH_UINT16(0x0053), // bnt 0053
17755 PATCH_ADDTOOFFSET(+75),
17756 0x74, PATCH_ADDTOOFFSET(+2), // lofss Hookah_Connected
17757 0x81, 0x09, // lag 09
17758 0x4a, PATCH_UINT16(0x000c), // send 0c
17759 0x39, PATCH_SELECTOR8(points), // pushi points
17760 0x78, // push1
17761 0x39, 0x03, // pushi 03
17762 PATCH_END
17763 };
17764
17765 // Filling the helmet in room 690 awards three points, but this happens every
17766 // time the player returns to fill the helmet with no limit. We fix this by
17767 // associating the points with flag 297 so that they're only awarded once, as
17768 // Sierra did in the French, German, and Mac versions.
17769 //
17770 // Applies to: English PC
17771 // Responsible method: sGetStuff:changeState(8)
17772 static const uint16 sq6DuplicatePointsSignature[] = {
17773 SIG_MAGICDWORD,
17774 0x39, SIG_SELECTOR8(points), // pushi points
17775 0x78, // push1
17776 0x39, 0x03, // pushi 03
17777 0x81, 0x01, // lag 01
17778 0x4a, SIG_UINT16(0x0006), // send 06 [ SQ6 points: 3 ]
17779 0x7e, // line
17780 SIG_END
17781 };
17782
17783 static const uint16 sq6DuplicatePointsPatch[] = {
17784 PATCH_ADDTOOFFSET(+2),
17785 0x7a, // push2
17786 PATCH_ADDTOOFFSET(+4),
17787 0x38, PATCH_UINT16(0x0129), // pushi 0129
17788 0x4a, PATCH_UINT16(0x0008), // send 08 [ SQ6 points: 3 297 ]
17789 PATCH_END
17790 };
17791
17792 // When attempting to restore a game that was saved with a different version of
17793 // the interpreter, SQ6 displays a standard error dialog but then crashes due
17794 // to a script bug. This also occurs in the original. SQ6 has custom code in
17795 // Game:restore to set and restore the cursor differently depending on the
17796 // current room. If in room 100, the main menu, then it attempts to restore the
17797 // cursor by sending a message to an uninitialized variable.
17798 //
17799 // We fix this by not restoring the cursor when temp1 is zero. This patch relies
17800 // on an existing uninitialized read workaround for Game:restore. The dialog
17801 // still renders poorly, as it does in the original, but that's unrelated.
17802 //
17803 // Applies to: All versions
17804 // Responsible method: Game:restore
17805 // Fixes bug: #9702
17806 static const uint16 sq6RestoreErrorDialogSignature[] = {
17807 0x38, SIG_SELECTOR16(setCursor), // pushi setCursor
17808 SIG_MAGICDWORD,
17809 0x7a, // push2
17810 0x8d, 0x01, // lst 01
17811 0x76, // push0
17812 0x43, 0x54, SIG_UINT16(0x0000), // callk HaveMouse 00
17813 0x36, // push
17814 0x54, SIG_UINT16(0x0008), // self 0008 [ self setCursor: temp1 kHaveMouse ]
17815 SIG_END
17816 };
17817
17818 static const uint16 sq6RestoreErrorDialogPatch[] = {
17819 0x85, 0x01, // lat 01
17820 0x30, PATCH_UINT16(0x000a), // bnt 000a [ skip setCursor if temp1 == 0 ]
17821 0x38, PATCH_SELECTOR16(setCursor), // pushi setCursor
17822 0x7a, // push2
17823 0x8d, 0x01, // lst 01
17824 0x78, // push1
17825 PATCH_END
17826 };
17827
17828 // script, description, signature patch
17829 static const SciScriptPatcherEntry sq6Signatures[] = {
17830 { true, 0, "fix slow transitions", 1, sq6SlowTransitionSignature2, sq6SlowTransitionPatch2 },
17831 { true, 15, "fix english pc missing points", 3, sq6MissingPointsSignature, sq6MissingPointsPatch },
17832 { true, 15, "fix staple/celery missing point", 2, sq6StapleCeleryPointSignature, sq6StapleCeleryPointPatch },
17833 { true, 15, "fix hookah hose missing point", 1, sq6HookahHosePointSignature, sq6HookahHosePointPatch },
17834 { true, 15, "fix invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch },
17835 { true, 22, "fix invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch },
17836 { true, 410, "fix slow transitions", 1, sq6SlowTransitionSignature2, sq6SlowTransitionPatch2 },
17837 { true, 460, "fix invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch },
17838 { true, 500, "fix slow transitions", 1, sq6SlowTransitionSignature1, sq6SlowTransitionPatch1 },
17839 { true, 510, "fix invalid array construction", 1, sci21IntArraySignature, sci21IntArrayPatch },
17840 { true, 690, "fix duplicate points", 1, sq6DuplicatePointsSignature, sq6DuplicatePointsPatch },
17841 { true, 64908, "disable video benchmarking", 1, sq6BenchmarkSignature, sci2BenchmarkPatch },
17842 { true, 64990, "increase number of save games (1/2)", 1, sci2NumSavesSignature1, sci2NumSavesPatch1 },
17843 { true, 64990, "increase number of save games (2/2)", 1, sci2NumSavesSignature2, sci2NumSavesPatch2 },
17844 { true, 64990, "disable change directory button", 1, sci2ChangeDirSignature, sci2ChangeDirPatch },
17845 { true, 64994, "fix restore-error dialog", 1, sq6RestoreErrorDialogSignature, sq6RestoreErrorDialogPatch },
17846 SCI_SIGNATUREENTRY_TERMINATOR
17847 };
17848
17849 #pragma mark -
17850 #pragma mark Torins Passage
17851
17852 // A subroutine that gets called by 'Torin::init' unconditionally resets the
17853 // audio volumes to defaults, but the game should always use the volume stored
17854 // in ScummVM. This patch is basically identical to the patch for LSL7, except
17855 // that they left line numbers in the LSL7 scripts and changed the music volume.
17856 // Applies to at least: English CD
17857 // Fixes bug: #9700
17858 static const uint16 torinVolumeResetSignature1[] = {
17859 SIG_MAGICDWORD,
17860 0x35, 0x28, // ldi $28
17861 0xa1, 0xe3, // sag global[$e3] (music volume)
17862 0x35, 0x3c, // ldi $3c
17863 0xa1, 0xe4, // sag global[$e4] (sfx volume)
17864 0x35, 0x64, // ldi $64
17865 0xa1, 0xe5, // sag global[$e5] (speech volume)
17866 SIG_END
17867 };
17868
17869 static const uint16 torinVolumeResetPatch1[] = {
17870 0x33, 0x0a, // jmp [past volume resets]
17871 PATCH_END
17872 };
17873
17874 // A subroutine that gets called by 'Torin::init' unconditionally resets the
17875 // audio volumes to values stored in torin.prf, but the game should always use
17876 // the volume stored in ScummVM. This patch is basically identical to the patch
17877 // for LSL7, except that they left line numbers in the LSL7 scripts.
17878 // Applies to at least: English CD
17879 // Fixes bug: #9700
17880 static const uint16 torinVolumeResetSignature2[] = {
17881 SIG_MAGICDWORD,
17882 0x38, SIG_SELECTOR16(readWord), // pushi readWord ($020b)
17883 0x76, // push0
17884 SIG_ADDTOOFFSET(+6), // ...
17885 0xa1, 0xe3, // sag global[$e3] (music volume)
17886 SIG_ADDTOOFFSET(+10), // ...
17887 0xa1, 0xe4, // sag global[$e4] (sfx volume)
17888 SIG_ADDTOOFFSET(+10), // ...
17889 0xa1, 0xe5, // sag global[$e5] (speech volume)
17890 SIG_END
17891 };
17892
17893 static const uint16 torinVolumeResetPatch2[] = {
17894 PATCH_ADDTOOFFSET(+10),
17895 0x18, 0x18, // (waste bytes)
17896 PATCH_ADDTOOFFSET(+10), // ...
17897 0x18, 0x18, // (waste bytes)
17898 PATCH_ADDTOOFFSET(+10), // ...
17899 0x18, 0x18, // (waste bytes)
17900 PATCH_END
17901 };
17902
17903 // In Escarpa, it is possible for Boogle to be left outside of Torin's bag
17904 // when fast-forwarding through the exit animation of the seraglio. If this
17905 // happens, when the player goes from the seraglio to the dragon's cave and
17906 // then tries to worm Boogle to the left side of the cave, the game will hang
17907 // because Boogle is on the wrong side of the navigable area barrier and cannot
17908 // move through it to continue the cutscene. This patch fixes the fast-forward
17909 // code 'soBoogleBackUp::ff' in the seraglio so that Boogle's in-the-bag flag
17910 // is set when fast-forwarding.
17911 // Applies to at least: English CD, Spanish CD
17912 // Fixes bug: #9836
17913 static const uint16 torinSeraglioBoogleFlagSignature[] = {
17914 0x35, 0x00, // ldi 0
17915 SIG_MAGICDWORD,
17916 0xa3, 0x00, // sal local[0]
17917 0x38, SIG_SELECTOR16(test), // pushi test
17918 SIG_ADDTOOFFSET(+0x5a), // all the rest of the method
17919 // CHECKME: Spanish version seems to have a total of 0x5d bytes from this point to the ret
17920 // FIXME: Check for end of method (e.g. ret) and add different signatures in case localized versions are different
17921 SIG_END
17922 };
17923
17924 static const uint16 torinSeraglioBoogleFlagPatch[] = {
17925 // @1e5f
17926 // ldi 0, sal local[0] removed from here (+4 bytes)
17927
17928 // @1e5f (+4 bytes)
17929 // local[0] = /* oFlags */ ScriptID(64017, 0);
17930 0x7a, // push2
17931 0x38, PATCH_UINT16(0xfa11), // pushi 64017
17932 0x76, // push0
17933 0x43, 0x02, PATCH_UINT16(0x0004), // callk ScriptID[2], 4
17934 0xa3, 0x00, // sal local[0] (-2 bytes)
17935
17936 // @1e6a (+2 bytes)
17937 // acc = local[0].test(94);
17938 0x38, PATCH_SELECTOR16(test), // pushi test
17939 0x78, // push1
17940 0x39, 0x5e, // pushi 94
17941 0x4a, PATCH_UINT16(0x0006), // send 6
17942
17943 // @1e73 (+2 bytes)
17944 // if (!acc) goto elseCase;
17945 0x30, PATCH_UINT16(0x0034), // bnt 0x31 + 3
17946
17947 // @1e76 (+2 bytes)
17948 // global[0].get(ScriptID(64001, 0).get(20));
17949 0x38, PATCH_SELECTOR16(get), // pushi get
17950 0x78, // push1
17951 0x38, PATCH_SELECTOR16(get), // pushi get
17952 0x78, // push1
17953 0x39, 0x14, // pushi 20
17954 0x7a, // push2
17955 0x38, PATCH_UINT16(0xfa01), // pushi 64001
17956 0x76, // push0
17957 0x43, 0x02, PATCH_UINT16(0x0004), // callk ScriptID[2], 4
17958 0x4a, PATCH_UINT16(0x0006), // send 6
17959 0x36, // push
17960 0x81, 0x00, // lag global[0] (ego)
17961 0x4a, PATCH_UINT16(0x0006), // send 6
17962
17963 // @1e92 (+2 bytes)
17964 // local[0].set(52);
17965 0x38, PATCH_SELECTOR16(set), // pushi set
17966 0x78, // push1
17967 0x39, 0x34, // pushi 52
17968 0x83, 0x00, // lal local[0] (+7 byte)
17969 0x4a, PATCH_UINT16(0x0006), // send 6
17970
17971 // @1e9d (+9 bytes)
17972 // goto endOfBranch;
17973 0x33, 0x0b, // jmp [to end of conditional branch] (+1 byte)
17974
17975 // @1e9f (+10 bytes)
17976 // elseCase: local[0].clear(97);
17977 0x38, PATCH_SELECTOR16(clear), // pushi clear
17978 0x78, // push1
17979 0x39, 0x61, // pushi 97
17980 0x83, 0x00, // lal local[0] (+7 bytes)
17981 0x4a, PATCH_UINT16(0x0006), // send 6
17982
17983 // @1eaa (+17 bytes)
17984 // endOfBranch: local[0].set(232);
17985 0x38, PATCH_SELECTOR16(set), // pushi set (-3 bytes)
17986 0x78, // push1 (-1 byte)
17987 0x38, PATCH_UINT16(0x00e8), // pushi 232 (Boogle-in-bag flag) (-3 bytes)
17988 0x83, 0x00, // lal local[0] (-2 bytes)
17989 0x4a, PATCH_UINT16(0x0006), // send 6 (-3 bytes)
17990
17991 // @1eb6 (+5 bytes)
17992 // local[0] = 0; self.dispose();
17993 0x38, PATCH_SELECTOR16(dispose), // pushi dispose
17994 0x76, // push0
17995 0x3c, // dup (-1 byte)
17996 0xab, 0x00, // ssl local[0] (-2 bytes)
17997 0x54, PATCH_UINT16(0x0004), // self 4
17998 0x48, // ret
17999
18000 // @1ec1 (+2 bytes)
18001 PATCH_END
18002 };
18003
18004 // At least some French PointSoft releases of Torin's Passage managed to get
18005 // released with script 20700 from the official Sierra TORINPAT patch and
18006 // *unpatched* heap 20700. Worse, the selector table is not the same as the one
18007 // in the US release, so it is not possible to just apply TORINPAT to the game
18008 // (it will just explode later when mismatched selectors are used). So, here we
18009 // are hot-patching all of the wrong offsets in the original heap to match the
18010 // patched script.
18011 // Applies to at least: French PointSoft CD release
18012 static const uint16 torinPointSoft20700HeapSignature[] = {
18013 0xe1, 0x15, 0x23, 0x16, // end of patched 20700.SCR (so we don't
18014 // accidentally patch the heap when it is correctly
18015 // matched with an unpatched script)
18016 SIG_ADDTOOFFSET(1), // padding byte added by Script::load
18017 SIG_ADDTOOFFSET(0x1d2), // first bad offset in the heap is at 0x1d2
18018 SIG_MAGICDWORD,
18019 SIG_UINT16(0xd8),
18020 SIG_UINT16(0xd8),
18021 SIG_ADDTOOFFSET(0x200 - 0x1d2 - 4), // second bad offset, etc.
18022 SIG_UINT16(0xde),
18023 SIG_UINT16(0xde),
18024 SIG_ADDTOOFFSET(0x280 - 0x200 - 4),
18025 SIG_UINT16(0xe0),
18026 SIG_UINT16(0xe0),
18027 SIG_ADDTOOFFSET(0x300 - 0x280 - 4),
18028 SIG_UINT16(0xe2),
18029 SIG_UINT16(0xe2),
18030 SIG_ADDTOOFFSET(0x374 - 0x300 - 4),
18031 SIG_UINT16(0xe4),
18032 SIG_UINT16(0xe4),
18033 SIG_ADDTOOFFSET(0x3ce - 0x374 - 4),
18034 SIG_UINT16(0xee),
18035 SIG_UINT16(0xee),
18036 SIG_ADDTOOFFSET(0x44e - 0x3ce - 4),
18037 SIG_UINT16(0xf0),
18038 SIG_UINT16(0xf0),
18039 SIG_ADDTOOFFSET(0x482 - 0x44e - 4),
18040 SIG_UINT16(0xf6),
18041 SIG_UINT16(0xf6),
18042 SIG_ADDTOOFFSET(0x4b6 - 0x482 - 4),
18043 SIG_UINT16(0xfc),
18044 SIG_UINT16(0xfc),
18045 SIG_ADDTOOFFSET(0x4ea - 0x4b6 - 4),
18046 SIG_UINT16(0x106),
18047 SIG_UINT16(0x106),
18048 SIG_ADDTOOFFSET(0x51e - 0x4ea - 4),
18049 SIG_UINT16(0x110),
18050 SIG_UINT16(0x110),
18051 SIG_ADDTOOFFSET(0x55c - 0x51e - 4),
18052 SIG_UINT16(0x116),
18053 SIG_UINT16(0x116),
18054 SIG_ADDTOOFFSET(0x5a2 - 0x55c - 4),
18055 SIG_UINT16(0x118),
18056 SIG_UINT16(0x118),
18057 SIG_END
18058 };
18059
18060 static const uint16 torinPointSoft20700HeapPatch[] = {
18061 PATCH_ADDTOOFFSET(4), // end of patched 20700.SCR
18062 PATCH_ADDTOOFFSET(1), // padding byte
18063 PATCH_ADDTOOFFSET(0x1d2), // first bad offset
18064 PATCH_UINT16(0xdc),
18065 PATCH_UINT16(0xdc),
18066 PATCH_ADDTOOFFSET(0x200 - 0x1d2 - 4), // second bad offset, etc.
18067 PATCH_UINT16(0xe6),
18068 PATCH_UINT16(0xe6),
18069 PATCH_ADDTOOFFSET(0x280 - 0x200 - 4),
18070 PATCH_UINT16(0xe8),
18071 PATCH_UINT16(0xe8),
18072 PATCH_ADDTOOFFSET(0x300 - 0x280 - 4),
18073 PATCH_UINT16(0xea),
18074 PATCH_UINT16(0xea),
18075 PATCH_ADDTOOFFSET(0x374 - 0x300 - 4),
18076 PATCH_UINT16(0xec),
18077 PATCH_UINT16(0xec),
18078 PATCH_ADDTOOFFSET(0x3ce - 0x374 - 4),
18079 PATCH_UINT16(0xf6),
18080 PATCH_UINT16(0xf6),
18081 PATCH_ADDTOOFFSET(0x44e - 0x3ce - 4),
18082 PATCH_UINT16(0xf8),
18083 PATCH_UINT16(0xf8),
18084 PATCH_ADDTOOFFSET(0x482 - 0x44e - 4),
18085 PATCH_UINT16(0xfe),
18086 PATCH_UINT16(0xfe),
18087 PATCH_ADDTOOFFSET(0x4b6 - 0x482 - 4),
18088 PATCH_UINT16(0x104),
18089 PATCH_UINT16(0x104),
18090 PATCH_ADDTOOFFSET(0x4ea - 0x4b6 - 4),
18091 PATCH_UINT16(0x10e),
18092 PATCH_UINT16(0x10e),
18093 PATCH_ADDTOOFFSET(0x51e - 0x4ea - 4),
18094 PATCH_UINT16(0x118),
18095 PATCH_UINT16(0x118),
18096 PATCH_ADDTOOFFSET(0x55c - 0x51e - 4),
18097 PATCH_UINT16(0x11e),
18098 PATCH_UINT16(0x11e),
18099 PATCH_ADDTOOFFSET(0x5a2 - 0x55c - 4),
18100 PATCH_UINT16(0x120),
18101 PATCH_UINT16(0x120),
18102 PATCH_END
18103 };
18104
18105 // script, description, signature patch
18106 static const SciScriptPatcherEntry torinSignatures[] = {
18107 { true, 20600, "fix wrong boogle bag flag on fast-forward", 1, torinSeraglioBoogleFlagSignature, torinSeraglioBoogleFlagPatch },
18108 { true, 20700, "fix bad heap in PointSoft release", 1, torinPointSoft20700HeapSignature, torinPointSoft20700HeapPatch },
18109 { true, 64000, "disable volume reset on startup (1/2)", 1, torinVolumeResetSignature1, torinVolumeResetPatch1 },
18110 { true, 64000, "disable volume reset on startup (2/2)", 1, torinVolumeResetSignature2, torinVolumeResetPatch2 },
18111 { true, 64866, "increase number of save games", 1, torinLarry7NumSavesSignature, torinLarry7NumSavesPatch },
18112 SCI_SIGNATUREENTRY_TERMINATOR
18113 };
18114
18115 #endif
18116
18117 // =================================================================================
18118
ScriptPatcher()18119 ScriptPatcher::ScriptPatcher() {
18120 int selectorCount = ARRAYSIZE(selectorNameTable);
18121 int selectorNr;
18122
18123 // Allocate table for selector-IDs and initialize that table as well
18124 _selectorIdTable = new Selector[ selectorCount ];
18125 for (selectorNr = 0; selectorNr < selectorCount; selectorNr++)
18126 _selectorIdTable[selectorNr] = -1;
18127
18128 _runtimeTable = NULL;
18129 _isMacSci11 = false;
18130 }
18131
~ScriptPatcher()18132 ScriptPatcher::~ScriptPatcher() {
18133 delete[] _runtimeTable;
18134 delete[] _selectorIdTable;
18135 }
18136
18137 // will actually patch previously found signature area
applyPatch(const SciScriptPatcherEntry * patchEntry,SciSpan<byte> scriptData,int32 signatureOffset)18138 void ScriptPatcher::applyPatch(const SciScriptPatcherEntry *patchEntry, SciSpan<byte> scriptData, int32 signatureOffset) {
18139 const uint16 *patchData = patchEntry->patchData;
18140 byte orgData[PATCH_VALUELIMIT];
18141 int32 offset = signatureOffset;
18142 uint16 patchWord = *patchEntry->patchData;
18143 uint16 patchSelector = 0;
18144
18145 // Copy over original bytes from script
18146 uint32 orgDataSize = scriptData.size() - offset;
18147 if (orgDataSize > PATCH_VALUELIMIT)
18148 orgDataSize = PATCH_VALUELIMIT;
18149 scriptData.subspan(offset, orgDataSize).unsafeCopyDataTo(orgData);
18150
18151 while (patchWord != PATCH_END) {
18152 uint16 patchCommand = patchWord & PATCH_COMMANDMASK;
18153 uint16 patchValue = patchWord & PATCH_VALUEMASK;
18154 switch (patchCommand) {
18155 case PATCH_CODE_ADDTOOFFSET: {
18156 // add value to offset
18157 offset += patchValue;
18158 break;
18159 }
18160 case PATCH_CODE_GETORIGINALBYTE: {
18161 // get original byte from script and adjust it
18162 if (patchValue >= orgDataSize)
18163 error("Script-Patcher: can not get requested original byte from script");
18164 byte orgByte = orgData[patchValue];
18165 int16 adjustValue;
18166 patchData++; adjustValue = (int16)(*patchData);
18167 scriptData[offset] = orgByte + adjustValue;
18168 offset++;
18169 break;
18170 }
18171 case PATCH_CODE_GETORIGINALUINT16: {
18172 // get original byte from script and adjust it
18173 if ((patchValue >= orgDataSize) || (((uint32)patchValue + 1) >= orgDataSize))
18174 error("Script-Patcher: can not get requested original uint16 from script");
18175 uint16 orgUINT16;
18176 int16 adjustValue;
18177
18178 if (!_isMacSci11) {
18179 orgUINT16 = orgData[patchValue] | (orgData[patchValue + 1] << 8);
18180 } else {
18181 orgUINT16 = orgData[patchValue + 1] | (orgData[patchValue] << 8);
18182 }
18183 patchData++; adjustValue = (int16)(*patchData);
18184 orgUINT16 += adjustValue;
18185 if (!_isMacSci11) {
18186 scriptData[offset] = orgUINT16 & 0xFF;
18187 scriptData[offset + 1] = orgUINT16 >> 8;
18188 } else {
18189 scriptData[offset] = orgUINT16 >> 8;
18190 scriptData[offset + 1] = orgUINT16 & 0xFF;
18191 }
18192 offset += 2;
18193 break;
18194 }
18195 case PATCH_CODE_UINT16:
18196 case PATCH_CODE_SELECTOR16: {
18197 byte byte1;
18198 byte byte2;
18199
18200 switch (patchCommand) {
18201 case PATCH_CODE_UINT16: {
18202 byte1 = patchValue & PATCH_BYTEMASK;
18203 patchData++; patchWord = *patchData;
18204 if (patchWord & PATCH_COMMANDMASK)
18205 error("Script-Patcher: Patch inconsistent");
18206 byte2 = patchWord & PATCH_BYTEMASK;
18207 break;
18208 }
18209 case PATCH_CODE_SELECTOR16: {
18210 patchSelector = _selectorIdTable[patchValue];
18211 byte1 = patchSelector & 0xFF;
18212 byte2 = patchSelector >> 8;
18213 break;
18214 }
18215 default:
18216 byte1 = 0; byte2 = 0;
18217 }
18218 if (!_isMacSci11) {
18219 scriptData[offset++] = byte1;
18220 scriptData[offset++] = byte2;
18221 } else {
18222 // SCI1.1+ on macintosh had uint16s in script in BE-order
18223 scriptData[offset++] = byte2;
18224 scriptData[offset++] = byte1;
18225 }
18226 break;
18227 }
18228 case PATCH_CODE_SELECTOR8: {
18229 patchSelector = _selectorIdTable[patchValue];
18230 if (patchSelector & 0xFF00)
18231 error("Script-Patcher: 8 bit selector required, game uses 16 bit selector");
18232 scriptData[offset] = patchSelector & 0xFF;
18233 offset++;
18234 break;
18235 }
18236 case PATCH_CODE_BYTE:
18237 scriptData[offset] = patchValue & PATCH_BYTEMASK;
18238 offset++;
18239 }
18240 patchData++;
18241 patchWord = *patchData;
18242 }
18243 }
18244
verifySignature(uint32 byteOffset,const uint16 * signatureData,const char * signatureDescription,const SciSpan<const byte> & scriptData)18245 bool ScriptPatcher::verifySignature(uint32 byteOffset, const uint16 *signatureData, const char *signatureDescription, const SciSpan<const byte> &scriptData) {
18246 uint16 sigSelector = 0;
18247
18248 uint16 sigWord = *signatureData;
18249 while (sigWord != SIG_END) {
18250 uint16 sigCommand = sigWord & SIG_COMMANDMASK;
18251 uint16 sigValue = sigWord & SIG_VALUEMASK;
18252 switch (sigCommand) {
18253 case SIG_CODE_ADDTOOFFSET: {
18254 // add value to offset
18255 byteOffset += sigValue;
18256 break;
18257 }
18258 case SIG_CODE_UINT16:
18259 case SIG_CODE_SELECTOR16: {
18260 if (byteOffset + 1 < scriptData.size()) {
18261 byte byte1;
18262 byte byte2;
18263
18264 switch (sigCommand) {
18265 case SIG_CODE_UINT16: {
18266 byte1 = sigValue & SIG_BYTEMASK;
18267 signatureData++; sigWord = *signatureData;
18268 if (sigWord & SIG_COMMANDMASK)
18269 error("Script-Patcher: signature inconsistent\nFaulty signature: '%s'", signatureDescription);
18270 byte2 = sigWord & SIG_BYTEMASK;
18271 break;
18272 }
18273 case SIG_CODE_SELECTOR16: {
18274 sigSelector = _selectorIdTable[sigValue];
18275 byte1 = sigSelector & 0xFF;
18276 byte2 = sigSelector >> 8;
18277 break;
18278 }
18279 default:
18280 byte1 = 0; byte2 = 0;
18281 }
18282 if (!_isMacSci11) {
18283 if ((scriptData[byteOffset] != byte1) || (scriptData[byteOffset + 1] != byte2))
18284 sigWord = SIG_MISMATCH;
18285 } else {
18286 // SCI1.1+ on macintosh had uint16s in script in BE-order
18287 if ((scriptData[byteOffset] != byte2) || (scriptData[byteOffset + 1] != byte1))
18288 sigWord = SIG_MISMATCH;
18289 }
18290 byteOffset += 2;
18291 } else {
18292 sigWord = SIG_MISMATCH;
18293 }
18294 break;
18295 }
18296 case SIG_CODE_SELECTOR8: {
18297 if (byteOffset < scriptData.size()) {
18298 sigSelector = _selectorIdTable[sigValue];
18299 if (sigSelector & 0xFF00)
18300 error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty signature: '%s'", signatureDescription);
18301 if (scriptData[byteOffset] != (sigSelector & 0xFF))
18302 sigWord = SIG_MISMATCH;
18303 byteOffset++;
18304 } else {
18305 sigWord = SIG_MISMATCH; // out of bounds
18306 }
18307 break;
18308 }
18309 case SIG_CODE_BYTE:
18310 if (byteOffset < scriptData.size()) {
18311 if (scriptData[byteOffset] != sigWord)
18312 sigWord = SIG_MISMATCH;
18313 byteOffset++;
18314 } else {
18315 sigWord = SIG_MISMATCH; // out of bounds
18316 }
18317 }
18318
18319 if (sigWord == SIG_MISMATCH)
18320 break;
18321
18322 signatureData++;
18323 sigWord = *signatureData;
18324 }
18325
18326 if (sigWord == SIG_END) // signature fully matched?
18327 return true;
18328 return false;
18329 }
18330
18331 // will return -1 if no match was found, otherwise an offset to the start of the signature match
findSignature(uint32 magicDWord,int magicOffset,const uint16 * signatureData,const char * patchDescription,const SciSpan<const byte> & scriptData)18332 int32 ScriptPatcher::findSignature(uint32 magicDWord, int magicOffset, const uint16 *signatureData, const char *patchDescription, const SciSpan<const byte> &scriptData) {
18333 if (scriptData.size() < 4) // we need to find a DWORD, so less than 4 bytes is not okay
18334 return -1;
18335
18336 // magicDWord is in platform-specific BE/LE form, so that the later match will work, this was done for performance
18337 const uint32 searchLimit = scriptData.size() - 3;
18338 uint32 DWordOffset = 0;
18339 // first search for the magic DWORD
18340 while (DWordOffset < searchLimit) {
18341 if (magicDWord == scriptData.getUint32At(DWordOffset)) {
18342 // magic DWORD found, check if actual signature matches
18343 uint32 offset = DWordOffset + magicOffset;
18344
18345 if (verifySignature(offset, signatureData, patchDescription, scriptData))
18346 return offset;
18347 }
18348 DWordOffset++;
18349 }
18350 // nothing found
18351 return -1;
18352 }
18353
findSignature(const SciScriptPatcherEntry * patchEntry,const SciScriptPatcherRuntimeEntry * runtimeEntry,const SciSpan<const byte> & scriptData)18354 int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, const SciScriptPatcherRuntimeEntry *runtimeEntry, const SciSpan<const byte> &scriptData) {
18355 return findSignature(runtimeEntry->magicDWord, runtimeEntry->magicOffset, patchEntry->signatureData, patchEntry->description, scriptData);
18356 }
18357
18358 // Attention: Magic DWord is returned using platform specific byte order. This is done on purpose for performance.
calculateMagicDWordAndVerify(const char * signatureDescription,const uint16 * signatureData,bool magicDWordIncluded,uint32 & calculatedMagicDWord,int & calculatedMagicDWordOffset)18359 void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescription, const uint16 *signatureData, bool magicDWordIncluded, uint32 &calculatedMagicDWord, int &calculatedMagicDWordOffset) {
18360 Selector curSelector = -1;
18361 int magicOffset;
18362 byte magicDWord[4];
18363 int magicDWordLeft = 0;
18364 uint16 curWord;
18365 uint16 curCommand;
18366 uint32 curValue;
18367 byte byte1 = 0;
18368 byte byte2 = 0;
18369
18370 memset(magicDWord, 0, sizeof(magicDWord));
18371
18372 curWord = *signatureData;
18373 magicOffset = 0;
18374 while (curWord != SIG_END) {
18375 curCommand = curWord & SIG_COMMANDMASK;
18376 curValue = curWord & SIG_VALUEMASK;
18377 switch (curCommand) {
18378 case SIG_MAGICDWORD: {
18379 if (magicDWordIncluded) {
18380 if ((calculatedMagicDWord) || (magicDWordLeft))
18381 error("Script-Patcher: Magic-DWORD specified multiple times in signature\nFaulty patch: '%s'", signatureDescription);
18382 magicDWordLeft = 4;
18383 calculatedMagicDWordOffset = magicOffset;
18384 } else {
18385 error("Script-Patcher: Magic-DWORD sequence found in patch data\nFaulty patch: '%s'", signatureDescription);
18386 }
18387 break;
18388 }
18389 case SIG_CODE_ADDTOOFFSET: {
18390 magicOffset -= curValue;
18391 if (magicDWordLeft)
18392 error("Script-Patcher: Magic-DWORD contains AddToOffset command\nFaulty patch: '%s'", signatureDescription);
18393 break;
18394 }
18395 case SIG_CODE_UINT16:
18396 case SIG_CODE_SELECTOR16: {
18397 // UINT16 or 1
18398 switch (curCommand) {
18399 case SIG_CODE_UINT16: {
18400 signatureData++; curWord = *signatureData;
18401 if (curWord & SIG_COMMANDMASK)
18402 error("Script-Patcher: signature entry inconsistent\nFaulty patch: '%s'", signatureDescription);
18403 if (!_isMacSci11) {
18404 byte1 = curValue;
18405 byte2 = curWord & SIG_BYTEMASK;
18406 } else {
18407 byte1 = curWord & SIG_BYTEMASK;
18408 byte2 = curValue;
18409 }
18410 break;
18411 }
18412 case SIG_CODE_SELECTOR16: {
18413 curSelector = _selectorIdTable[curValue];
18414 if (curSelector == -1) {
18415 curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]);
18416 _selectorIdTable[curValue] = curSelector;
18417 }
18418 if (!_isMacSci11) {
18419 byte1 = curSelector & 0x00FF;
18420 byte2 = curSelector >> 8;
18421 } else {
18422 byte1 = curSelector >> 8;
18423 byte2 = curSelector & 0x00FF;
18424 }
18425 break;
18426 }
18427 }
18428 magicOffset -= 2;
18429 if (magicDWordLeft) {
18430 // Remember current word for Magic DWORD
18431 magicDWord[4 - magicDWordLeft] = byte1;
18432 magicDWordLeft--;
18433 if (magicDWordLeft) {
18434 magicDWord[4 - magicDWordLeft] = byte2;
18435 magicDWordLeft--;
18436 }
18437 if (!magicDWordLeft) {
18438 // Magic DWORD is now known, convert to platform specific byte order
18439 calculatedMagicDWord = READ_UINT32(magicDWord);
18440 }
18441 }
18442 break;
18443 }
18444 case SIG_CODE_BYTE:
18445 case SIG_CODE_SELECTOR8: {
18446 if (curCommand == SIG_CODE_SELECTOR8) {
18447 curSelector = _selectorIdTable[curValue];
18448 if (curSelector == -1) {
18449 curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]);
18450 _selectorIdTable[curValue] = curSelector;
18451 if (curSelector != -1) {
18452 if (curSelector & 0xFF00)
18453 error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty patch: '%s'", signatureDescription);
18454 }
18455 }
18456 curValue = curSelector;
18457 }
18458 magicOffset--;
18459 if (magicDWordLeft) {
18460 // Remember current byte for Magic DWORD
18461 magicDWord[4 - magicDWordLeft] = (byte)curValue;
18462 magicDWordLeft--;
18463 if (!magicDWordLeft) {
18464 // Magic DWORD is now known, convert to platform specific byte order
18465 calculatedMagicDWord = READ_UINT32(magicDWord);
18466 }
18467 }
18468 break;
18469 }
18470 case PATCH_CODE_GETORIGINALBYTE:
18471 case PATCH_CODE_GETORIGINALUINT16: {
18472 signatureData++; // skip over extra uint16
18473 break;
18474 }
18475 default:
18476 break;
18477 }
18478 signatureData++;
18479 curWord = *signatureData;
18480 }
18481
18482 if (magicDWordLeft)
18483 error("Script-Patcher: Magic-DWORD beyond End-Of-Signature\nFaulty patch: '%s'", signatureDescription);
18484 if (magicDWordIncluded) {
18485 if (!calculatedMagicDWord) {
18486 error("Script-Patcher: Magic-DWORD not specified in signature\nFaulty patch: '%s'", signatureDescription);
18487 }
18488 }
18489 }
18490
18491 // This method calculates the magic DWORD for each entry in the signature table
18492 // and it also initializes the selector table for selectors used in the signatures/patches of the current game
initSignature(const SciScriptPatcherEntry * patchTable)18493 void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable) {
18494 const SciScriptPatcherEntry *curEntry = patchTable;
18495 SciScriptPatcherRuntimeEntry *curRuntimeEntry;
18496 int patchEntryCount = 0;
18497
18498 // Count entries and allocate runtime data
18499 while (curEntry->signatureData) {
18500 patchEntryCount++; curEntry++;
18501 }
18502 _runtimeTable = new SciScriptPatcherRuntimeEntry[patchEntryCount];
18503 memset(_runtimeTable, 0, sizeof(SciScriptPatcherRuntimeEntry) * patchEntryCount);
18504
18505 curEntry = patchTable;
18506 curRuntimeEntry = _runtimeTable;
18507 while (curEntry->signatureData) {
18508 // process signature
18509 curRuntimeEntry->active = curEntry->defaultActive;
18510 curRuntimeEntry->magicDWord = 0;
18511 curRuntimeEntry->magicOffset = 0;
18512
18513 // We verify the signature data and remember the calculated magic DWord from the signature data
18514 calculateMagicDWordAndVerify(curEntry->description, curEntry->signatureData, true, curRuntimeEntry->magicDWord, curRuntimeEntry->magicOffset);
18515 // We verify the patch data
18516 calculateMagicDWordAndVerify(curEntry->description, curEntry->patchData, false, curRuntimeEntry->magicDWord, curRuntimeEntry->magicOffset);
18517
18518 curEntry++; curRuntimeEntry++;
18519 }
18520 }
18521
18522 // This method enables certain patches
18523 // It's used for patches, which are not meant to get applied all the time
enablePatch(const SciScriptPatcherEntry * patchTable,const char * searchDescription)18524 void ScriptPatcher::enablePatch(const SciScriptPatcherEntry *patchTable, const char *searchDescription) {
18525 const SciScriptPatcherEntry *curEntry = patchTable;
18526 SciScriptPatcherRuntimeEntry *runtimeEntry = _runtimeTable;
18527 int searchDescriptionLen = strlen(searchDescription);
18528 int matchCount = 0;
18529
18530 while (curEntry->signatureData) {
18531 if (strncmp(curEntry->description, searchDescription, searchDescriptionLen) == 0) {
18532 // match found, enable patch
18533 runtimeEntry->active = true;
18534 matchCount++;
18535 }
18536 curEntry++; runtimeEntry++;
18537 }
18538
18539 if (!matchCount)
18540 error("Script-Patcher: no patch found to enable");
18541 }
18542
processScript(uint16 scriptNr,SciSpan<byte> scriptData)18543 void ScriptPatcher::processScript(uint16 scriptNr, SciSpan<byte> scriptData) {
18544 const SciScriptPatcherEntry *signatureTable = NULL;
18545 const SciScriptPatcherEntry *curEntry = NULL;
18546 SciScriptPatcherRuntimeEntry *curRuntimeEntry = NULL;
18547 const Sci::SciGameId gameId = g_sci->getGameId();
18548
18549 switch (gameId) {
18550 case GID_CAMELOT:
18551 signatureTable = camelotSignatures;
18552 break;
18553 case GID_ECOQUEST:
18554 signatureTable = ecoquest1Signatures;
18555 break;
18556 case GID_ECOQUEST2:
18557 signatureTable = ecoquest2Signatures;
18558 break;
18559 case GID_FANMADE:
18560 signatureTable = fanmadeSignatures;
18561 break;
18562 case GID_FREDDYPHARKAS:
18563 signatureTable = freddypharkasSignatures;
18564 break;
18565 case GID_HOYLE4:
18566 signatureTable = hoyle4Signatures;
18567 break;
18568 #ifdef ENABLE_SCI32
18569 case GID_HOYLE5:
18570 if (g_sci->getResMan()->testResource(ResourceId(kResourceTypeScript, 100)) &&
18571 g_sci->getResMan()->testResource(ResourceId(kResourceTypeScript, 700)))
18572 signatureTable = hoyle5Signatures;
18573 else if (g_sci->getResMan()->testResource(ResourceId(kResourceTypeScript, 100)) &&
18574 !g_sci->getResMan()->testResource(ResourceId(kResourceTypeScript, 700)))
18575 signatureTable = hoyle5ChildrensCollectionSignatures;
18576 else if (!g_sci->getResMan()->testResource(ResourceId(kResourceTypeScript, 100)) &&
18577 g_sci->getResMan()->testResource(ResourceId(kResourceTypeScript, 700)))
18578 signatureTable = hoyle5BridgeSignatures;
18579 break;
18580 case GID_GK1:
18581 signatureTable = gk1Signatures;
18582 break;
18583 case GID_GK2:
18584 signatureTable = gk2Signatures;
18585 break;
18586 #endif
18587 case GID_ICEMAN:
18588 signatureTable = icemanSignatures;
18589 break;
18590 case GID_KQ5:
18591 signatureTable = kq5Signatures;
18592 break;
18593 case GID_KQ6:
18594 signatureTable = kq6Signatures;
18595 break;
18596 #ifdef ENABLE_SCI32
18597 case GID_KQ7:
18598 signatureTable = kq7Signatures;
18599 break;
18600 #endif
18601 case GID_LAURABOW:
18602 signatureTable = laurabow1Signatures;
18603 break;
18604 case GID_LAURABOW2:
18605 signatureTable = laurabow2Signatures;
18606 break;
18607 #ifdef ENABLE_SCI32
18608 case GID_LIGHTHOUSE:
18609 signatureTable = lighthouseSignatures;
18610 break;
18611 #endif
18612 case GID_LONGBOW:
18613 signatureTable = longbowSignatures;
18614 break;
18615 case GID_LSL1:
18616 signatureTable = larry1Signatures;
18617 break;
18618 case GID_LSL2:
18619 signatureTable = larry2Signatures;
18620 break;
18621 case GID_LSL5:
18622 signatureTable = larry5Signatures;
18623 break;
18624 case GID_LSL6:
18625 signatureTable = larry6Signatures;
18626 break;
18627 #ifdef ENABLE_SCI32
18628 case GID_LSL6HIRES:
18629 signatureTable = larry6HiresSignatures;
18630 break;
18631 case GID_LSL7:
18632 signatureTable = larry7Signatures;
18633 break;
18634 #endif
18635 case GID_MOTHERGOOSE256:
18636 signatureTable = mothergoose256Signatures;
18637 break;
18638 #ifdef ENABLE_SCI32
18639 case GID_MOTHERGOOSEHIRES:
18640 signatureTable = mothergooseHiresSignatures;
18641 break;
18642
18643 case GID_PHANTASMAGORIA:
18644 signatureTable = phantasmagoriaSignatures;
18645 break;
18646
18647 case GID_PHANTASMAGORIA2:
18648 signatureTable = phantasmagoria2Signatures;
18649 break;
18650 #endif
18651 case GID_PQ1:
18652 signatureTable = pq1vgaSignatures;
18653 break;
18654 case GID_PQ3:
18655 signatureTable = pq3Signatures;
18656 break;
18657 #ifdef ENABLE_SCI32
18658 case GID_PQ4:
18659 signatureTable = pq4Signatures;
18660 break;
18661 case GID_PQSWAT:
18662 signatureTable = pqSwatSignatures;
18663 break;
18664 #endif
18665 case GID_QFG1:
18666 signatureTable = qfg1egaSignatures;
18667 break;
18668 case GID_QFG1VGA:
18669 signatureTable = qfg1vgaSignatures;
18670 break;
18671 case GID_QFG2:
18672 signatureTable = qfg2Signatures;
18673 break;
18674 case GID_QFG3:
18675 signatureTable = qfg3Signatures;
18676 break;
18677 #ifdef ENABLE_SCI32
18678 case GID_QFG4:
18679 signatureTable = qfg4Signatures;
18680 break;
18681 case GID_RAMA:
18682 signatureTable = ramaSignatures;
18683 break;
18684 case GID_SHIVERS:
18685 signatureTable = shiversSignatures;
18686 break;
18687 #endif
18688 case GID_SQ1:
18689 signatureTable = sq1vgaSignatures;
18690 break;
18691 case GID_SQ4:
18692 signatureTable = sq4Signatures;
18693 break;
18694 case GID_SQ5:
18695 signatureTable = sq5Signatures;
18696 break;
18697 #ifdef ENABLE_SCI32
18698 case GID_SQ6:
18699 signatureTable = sq6Signatures;
18700 break;
18701 case GID_TORIN:
18702 signatureTable = torinSignatures;
18703 break;
18704 #endif
18705 default:
18706 break;
18707 }
18708
18709 if (signatureTable) {
18710 _isMacSci11 = (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1);
18711
18712 if (!_runtimeTable) {
18713 // signature table needs to get initialized (Magic DWORD set, selector table set)
18714 initSignature(signatureTable);
18715
18716 // Do additional game-specific initialization
18717 switch (gameId) {
18718 case GID_FREDDYPHARKAS:
18719 if (_isMacSci11 && !g_sci->getResMan()->testResource(ResourceId(kResourceTypeView, 844))) {
18720 enablePatch(signatureTable, "Mac: skip broken hop singh scene");
18721 }
18722 break;
18723 case GID_GK2:
18724 // Enable subtitle compatibility if a sync resource is present
18725 if (g_sci->getResMan()->testResource(ResourceId(kResourceTypeSync, 10))) {
18726 enablePatch(signatureTable, "subtitle patch compatibility");
18727 }
18728 break;
18729 case GID_KQ5:
18730 if (g_sci->_features->useAltWinGMSound()) {
18731 // See the explanation in the kq5SignatureWinGMSignals comment
18732 enablePatch(signatureTable, "Win: GM Music signal checks");
18733 }
18734 break;
18735 case GID_KQ6:
18736 if (g_sci->isCD()) {
18737 // Enables Dual mode patches (audio + subtitles at the same time) for King's Quest 6
18738 enablePatch(signatureTable, "CD: audio + text support");
18739 }
18740 if (_isMacSci11) {
18741 // Enables Mac-only patch to work around missing pic
18742 enablePatch(signatureTable, "Mac: Drink Me pic");
18743 }
18744 break;
18745 case GID_LAURABOW2:
18746 if (g_sci->isCD()) {
18747 // Enables Dual mode patches (audio + subtitles at the same time) for Laura Bow 2
18748 enablePatch(signatureTable, "CD: audio + text support");
18749 }
18750 break;
18751 case GID_QFG4:
18752 if (g_sci->isCD()) {
18753 // Floppy doesn't need this
18754 enablePatch(signatureTable, "CD: fix rope during Igor rescue (1/2)");
18755 enablePatch(signatureTable, "CD: fix rope during Igor rescue (2/2)");
18756
18757 // Similar signatures that patch with different addresses/offsets
18758 enablePatch(signatureTable, "CD: fix guild tunnel access (3/3)");
18759 enablePatch(signatureTable, "CD: fix crest bookshelf");
18760 enablePatch(signatureTable, "CD: fix peer bats, upper door (2/2)");
18761 } else {
18762 enablePatch(signatureTable, "Floppy: fix guild tunnel access (3/3)");
18763 enablePatch(signatureTable, "Floppy: fix crest bookshelf");
18764 enablePatch(signatureTable, "Floppy: fix peer bats, upper door (2/2)");
18765 }
18766 break;
18767 case GID_SQ4:
18768 // Enable the dress-purchase flag fixes for English Amiga only.
18769 // One of these patches is applied to scripts that are the same as those
18770 // in other versions which must not be patched, including German Amiga.
18771 if (g_sci->getPlatform() == Common::kPlatformAmiga) {
18772 // Check for buggy Sock's (room 371) script from English Amiga version
18773 Resource *socksScript = g_sci->getResMan()->findResource(ResourceId(kResourceTypeScript, 371), false);
18774 if (socksScript && socksScript->size() == 14340) {
18775 enablePatch(signatureTable, "Amiga: dress purchase flag check fix");
18776 enablePatch(signatureTable, "Amiga: dress purchase flag clear fix");
18777 }
18778 }
18779
18780 if (g_sci->isCD() && !g_sci->getResMan()->testResource(ResourceId(kResourceTypeScript, 271))) {
18781 enablePatch(signatureTable, "CD: disable timepod code for removed room");
18782 }
18783 break;
18784 default:
18785 break;
18786 }
18787 }
18788
18789 curEntry = signatureTable;
18790 curRuntimeEntry = _runtimeTable;
18791
18792 while (curEntry->signatureData) {
18793 if ((scriptNr == curEntry->scriptNr) && (curRuntimeEntry->active)) {
18794 int32 foundOffset = 0;
18795 int16 applyCount = curEntry->applyCount;
18796 do {
18797 foundOffset = findSignature(curEntry, curRuntimeEntry, scriptData);
18798 if (foundOffset != -1) {
18799 // found, so apply the patch
18800 debugC(kDebugLevelScriptPatcher, "Script-Patcher: '%s' on script %d offset %d", curEntry->description, scriptNr, foundOffset);
18801 applyPatch(curEntry, scriptData, foundOffset);
18802 }
18803 applyCount--;
18804 } while ((foundOffset != -1) && (applyCount));
18805 }
18806 curEntry++; curRuntimeEntry++;
18807 }
18808 }
18809 }
18810
18811 } // End of namespace Sci
18812