The Last Crusade: hidden ending

Started by Radiant, Fri 05/12/2014 13:37:07

Previous topic - Next topic

Radiant

I'm sure many people here have played the LucasArts classic, Indiana Jones and the Last Crusade.

The official ending has Indy returning the Grail to the last knight, because the Grail cannot be taken out of the temple (indeed, you can try to, but you'll get killed as a result). However, the game script suggests otherwise: it actually contains lines for Henry that he's "so ashamed... we barge into this knight's house, steal his grail, and completely wreck the place" (paraphrased). Fiddling around with the ScummVM debug mode I can indeed cause this scene to happen.

I'm curious, though, how do you trigger this scene legitimately? What do I need to do in-game to actually steal the grail, does anyone know?

Mandle

Unless it is just an alternative ending that never got into the game...

mkennedy

Spoiler
When I played the game there was an earthquake and the grail fell in a chasm as I recall so I left it there. Maybe Indy could have used his whip to pull it up?
[close]

Radiant

Quote from: Mandle on Fri 05/12/2014 14:36:22
Unless it is just an alternative ending that never got into the game...
Well, that's why I'm asking the foremost forum of adventure game experts, right here :)

Quote from: mkennedy on Mon 08/12/2014 01:12:48
Spoiler
When I played the game there was an earthquake and the grail fell in a chasm as I recall so I left it there. Maybe Indy could have used his whip to pull it up?
[close]
Yes, you can do that. The obvious paths through this puzzle are,

(1) Pick up the grail, give it to the knight; Indy and Elsa leave with Henry and Marcus.
(2) Pick up the grail, walk out of the temple, a chasm opens under you and you die.
(3) Pick up the grail, give it to Elsa; OR just wait and she'll pick it up herself; OR try to walk out and she'll also pick it up. A chasm opens under Elsa and she dies.
(3A) now you can walk out; Indy leaves with Henry and Marcus.
(3B) or you can pick up the grail with your whip and give it to the knight; Indy again leaves with Henry and Marcus.
(3C) or you can pick up the grail with your whip and walk out, and a chasm opens under you and you die.

... and according to one walkthrough I've found, option 3C sometimes allows you to leave with the grail and get this special ending. But I haven't been able to reproduce that, so apparently I'm missing something...

mkennedy

#4
Maybe punch the knight?

Edit: think I did ending 3A, It was long ago so my memory may be off - walked out and Elsa grabbed the grail, and she fell in the chasm. Does a second chasm open after the first one to kill Indy also if he grabs the grail from the first chasm?

Radiant

Quote from: mkennedy on Wed 10/12/2014 06:39:57
Maybe punch the knight?
How? There is no "punch" command.

QuoteEdit: think I did ending 3A, It was long ago so my memory may be off - walked out and Elsa grabbed the grail, and she fell in the chasm. Does a second chasm open after the first one to kill Indy also if he grabs the grail from the first chasm?
Yes, it does. That would be ending 3C.

Monsieur OUXX

I'm almost certain it's not possible. Those classic games have been worn out in every possible way -- if it were possible, someone would have posted a video in Youtube. I would suspect it's one of the many things they left in the script to avoid breaking something at release, but that is not connected to the actual game. A bit like the unused inventory items.
 

LostTrainDude

I wouldn't be surprised if it was something that nobody discovered until now (at least, publicly). I recall of this Final Fantasy 9 quest that stayed apparently unnoticed (I'm no hardcore fan of the series, though, so I don't really know) until last year.

Also, I would be geekly excited if it was something "hidden" and left un-implemented :D
I think that since both David Fox and Noah Falstein have their own Twitter account, we could even dare to ask them! :D

Few years ago I've attended a Falstein's talk about his Lucas' years and I recall he talked a lot about The Last Crusade. I do remember him explaining the different endings, but I don't remember if he also talked about this specifically.
"We do not stop playing because we grow old, we grow old because we stop playing."

Radiant

If anyone's in touch with the ScummVM developers, they could surely open the TLC script and see how this is triggered.

Mandle

Quote from: Radiant on Fri 12/12/2014 14:40:47
If anyone's in touch with the ScummVM developers, they could surely open the TLC script and see how this is triggered.

I have the theory that it's something the game designers started work on and when they presented it to George Lucas he said "What the...?! Are you all nuts??? You can't have Indy stealing the Grail!!! That would be totally nuking-the-fridge!!!" and vetoed it, and so they unlinked it from the gameplay and "forgot" to remove their hard work from the code. In which case there may be a back-door to it they put in just to show it off to impress girls at parties ;)

Gabarts

Quote from: Mandle on Fri 12/12/2014 22:40:20
Quote from: Radiant on Fri 12/12/2014 14:40:47
If anyone's in touch with the ScummVM developers, they could surely open the TLC script and see how this is triggered.

I have the theory that it's something the game designers started work on and when they presented it to George Lucas he said "What the...?! Are you all nuts??? You can't have Indy stealing the Grail!!! That would be totally nuking-the-fridge!!!" and vetoed it, and so they unlinked it from the gameplay and "forgot" to remove their hard work from the code. In which case there may be a back-door to it they put in just to show it off to impress girls at parties ;)

In my adventure Indy got the Grail somehow :)

mkennedy

Quote from: Radiant on Thu 11/12/2014 10:38:18
Quote from: mkennedy on Wed 10/12/2014 06:39:57
Maybe punch the knight?
How? There is no "punch" command.

Wasn't there a "throw a punch" option that was sometimes available? Think I tried punching Hitler and his goons shot me dead. Though it might not be available all the time since we probably wouldn't want Indy punching Henry, Marcus, or Elsa.

Indy: You've called me 'Junior' once too often, old man! (Punches his dad's lights out)

Trapezoid

Quote from: Monsieur OUXX on Thu 11/12/2014 16:42:02
I'm almost certain it's not possible. Those classic games have been worn out in every possible way -- if it were possible, someone would have posted a video in Youtube. I would suspect it's one of the many things they left in the script to avoid breaking something at release, but that is not connected to the actual game. A bit like the unused inventory items.
Stuff like unused art were always easily findable with stuff like ScummRevisited, but as far as I know, being able to look at decompiled SCUMM scripts is a relatively new thing, and they haven't been picked over nearly as much. Though you're right, it probably is unreachable code.

Radiant

Quote from: mkennedy on Sat 13/12/2014 08:53:37
Wasn't there a "throw a punch" option that was sometimes available?
Yes, as a dialog option when you're talking to people.

Quote from: Trapezoid on Mon 15/12/2014 05:57:10
Stuff like unused art were always easily findable with stuff like ScummRevisited, but as far as I know, being able to look at decompiled SCUMM scripts is a relatively new thing, and they haven't been picked over nearly as much. Though you're right, it probably is unreachable code.
How do I decompile a SCUMM script, then?

Monsieur OUXX

Quote from: Trapezoid on Mon 15/12/2014 05:57:10
Stuff like unused art were always easily findable with stuff like ScummRevisited, but as far as I know, being able to look at decompiled SCUMM scripts is a relatively new thing.
That makes sense. I'm getting excited now.
@Radiant: I don't know what is the tool Trapezoid is mentioning. I'm not sure he means that there is a decompiler, but maybe just that the script ca now be loaded and executed step by step int he ScummVM interpreter, so that could be an entry point for a script viewer.

EDIT: http://wiki.scummvm.org/index.php/OpenTasks/Tools/Game_script_decompiler
 

Trapezoid

Quote from: Radiant on Mon 15/12/2014 09:50:48
How do I decompile a SCUMM script, then?
Oh, I have no idea. I'm pretty sure I've seen examples of people analyzing SCUMM code before. How did you find out about this hidden ending if not by looking at the script?

edmundito

#16
Heh, sorry I'm bumping this up but I was doing a google search on how to do this and stumbled upon your question. (I've been playing Grim Fandango HD recently and I've learned that there's enough tools out there to reverse engineer these games.)

Here's one way to do it:

Download SCUMM Revisited from: http://quick.mixnmojo.com/files/ScummRev2.zip
Open the .LFL files in the INDY directory
Find each node in the tree that is marked as "SC" or "LS" is a script. Click on "File Dump..."
Then clone and build the SCUMM VM tools (here's how you would do it with cygwin or on linux/mac):
Quote
git clone https://github.com/scummvm/scummvm-tools.git
cd scummvm-tools
./configure
make

Then this will create a tool called descumm that decompiles the script

Quote
./descumm -xcn SL.srb

(The -n is for Indy 256. You can figure out which version of SCUMM your game is from at: http://wiki.scummvm.org/index.php/SCUMM/Versions)

You will then see something like this:
Code: ags

Script# 202
delay(600);
VAR_RESULT = getActorX(1);
if (VAR_RESULT == 45) {
  VAR_RESULT = getActorY(1);
  if (VAR_RESULT == 94) {
    cutscene([]);
    animateCostume(1,255);
    print(255,[Color(7),Text("Indy!!!  Hurry up!  Your father is getting worse!!")]);
    delay(20);
    animateCostume(1,244);
    WaitForMessage();
    print(1,[Text("I know Marcus!  I'm hurrying!")]);
    walkActorTo(1,18,96);
    waitForActor(1);
    WaitForMessage();
    delay(20);
    animateCostume(1,245);
    endCutscene();
  }
}
stopObjectCode();
END


(Don't ask me how I just figured out how to do all this.)

edmundito

#17
Based on what was mentioned in the first post ("Well this is just grand! We meet a living veteran of the Crusades, steal his Grail, and then completely destroy his temple! All in the name of Archaeology? I will never do anything like this again."), the script in question is in the global script in room 82. The line is there, but seems completely random. I've attached the script.

Code: ags

[0000] cutscene([1]);
[0005] Var[100] = VAR_CAMERA_POS_X;
[000A] breakHere();
[000B] unless (VAR_CAMERA_POS_X == Var[100]) goto 0005;
[0012] if (Var[99] == 1) {
[0019]   print(2,[Text("That was a just and noble thing you did, Junior.")]);
[004D]   WaitForMessage();
[004E]   print(1,[Text("Don't call me Junior!")]);
[0067]   WaitForMessage();
[0068]   print(4,[Text("What is all this Junior talk?")]);
[0089]   WaitForMessage();
[008A]   print(2,[Text("That's his name:" + wait() + "Henry Jones, Jr.")]);
[00B0]   WaitForMessage();
[00B1]   print(1,[Text("I like Indiana.")]);
[00C4]   WaitForMessage();
[00C5]   print(2,[Text("We named the DOG Indiana!" + wait() + "We named YOU Henry, Jr.")]);
[00FB]   WaitForMessage();
[00FC]   print(4,[Text("Come to think of it, my father had a cat named `Marcus`.")]);
[0138]   WaitForMessage();
[0139]   VAR_RESULT = getActorRoom(3);
[013D]   if (VAR_RESULT == 82) {
[0144]     print(3,[Text("Hmm.  Wasn't my father's secretary named `Elsa`?")]);
[0178]     WaitForMessage();
[0179]   }
[0179]   print(2,[Text("Enough!  Let's go home." + wait() + "I think I'll start a Dead Sea Scrolls Diary.")]);
[01C2]   WaitForMessage();
[01C3]   print(4,[Text("Everybody, follow me, I know the way!")]);
[01EC]   delay(60);
[01F0]   panCameraTo(160);
[01F3]   WaitForMessage();
[01F4]   breakHere();
[01F5]   unless (VAR_CAMERA_POS_X == 160) goto 01F4;
[01FC]   walkActorTo(4,400,120);
[0202]   breakHere();
[0203]   breakHere();
[0204]   startScript(56,[4,1],R);
[020D]   startScript(56,[1,2],R);
[0216]   VAR_RESULT = getActorRoom(3);
[021A]   if (VAR_RESULT == 82) {
[0221]     startScript(56,[2,3],R);
[022A]   }
[022A]   waitForActor(4);
[022C]   delay(120);
[0230]   print(4,[Text("No, that's not right.")]);
[0249]   WaitForMessage();
[024A]   walkActorToObject(4,861);
[024E]   waitForActor(4);
[0250]   stopScript(56);
[0252] } else if (Var[99] == 2) {
[025C]   VAR_EGO = 2;
[0261]   actorFollowCamera(VAR_EGO);
[0264]   breakHere();
[0265]   unless (VAR_CAMERA_POS_X <= 160) goto 0264;
[026C]   print(2,[Text("Junior!  Why didn't you listen!")]);
[028F]   WaitForMessage();
[0290]   print(255,[Color(14),Text("Don't call me JUUUUNIOOOOOooooooorrrrr^")]);
[02BD]   WaitForMessage();
[02BE]   delay(30);
[02C2] } else if (Var[99] == 3) {
[02CC]   print(3,[Text("Oh, Indy!" + wait() + "If only you'd let me try, I could have taken the Grail" + newline() + "for the two of us!")]);
[0325]   WaitForMessage();
[0326]   VAR_RESULT = getRandomNr(1);
[032A]   if (VAR_RESULT) {
[032F]     print(2,[Text("The Grail is not a prize to be won." + wait() + "Junior knows that now.")]);
[036E]     WaitForMessage();
[036F]   } else {
[0372]     animateCostume(4,4);
[0375]     animateCostume(2,4);
[0378]     print(255,[Color(10),Text("Don't bet on it. " + keepText())]);
[0391]     print(255,[Color(7),Text("Don't bet on it.                     ")]);
[03BC]     WaitForMessage();
[03BD]     animateCostume(4,5);
[03C0]     animateCostume(2,5);
[03C3]   }
[03C3]   delay(30);
[03C7] } else if (Var[99] == 4) {
[03D1]   startScript(125,[71,50]);
[03DA]   VAR_RESULT = getRandomNr(1);
[03DE]   if (VAR_RESULT) {
[03E3]     print(2,[Text("Well this is just grand^" + wait() + "We meet a living veteran of the Crusades, steal his Grail," + wait() + "and then completely destroy his temple!" + wait() + "All in the name of Archaeology?" + wait() + "I will never do anything like this again.")]);
[04B0]     WaitForMessage();
[04B1]     print(1,[Text("Hey Dad, I think the Knight muttered something about his" + newline() + "friend, Ponce, who's living in Florida!")]);
[0516]     WaitForMessage();
[0517]     print(2,[Text("Let's go find him!!!")]);
[052F]   } else {
[0532]     print(2,[Text("Son, I'm proud of you." + wait() + "The quest for the Grail is the quest for the sacred part" + newline() + "in each of us." + wait() + "I can see that you found something more than just" + newline() + "a prize to be won.")]);
[05DD]     WaitForMessage();
[05DE]     print(1,[Text("And what did you find, Dad?")]);
[05FD]     WaitForMessage();
[05FE]     animateCostume(2,250);
[0601]     delay(60);
[0605]     print(2,[Text("Enlightenment.")]);
[0617]     WaitForMessage();
[0618]     animateCostume(2,249);
[061B]     print(2,[Text("Let's go, son.")]);
[062D]   }
[062D]   WaitForMessage();
[062E]   walkActorToObject(2,861);
[0632]   breakHere();
[0633]   breakHere();
[0634]   breakHere();
[0635]   breakHere();
[0636]   walkActorToObject(1,861);
[063A]   waitForActor(2);
[063C]   /* goto 063F; */
[063F] }
[063F] loadRoom(94);
[0641] delay(36000);
[0645] goto 0641;
[0648] endCutscene();
[0649] stopObjectCode();
END


Seems like it's a ~50% chance if you can get to the right ending sequence.

Here's another script in the room that sets Var[99] (the end condition)
Code: ags

[0000] breakHere();
[0001] VAR_RESULT = getActorX(1);
[0005] unless (VAR_RESULT < 300) goto 0000;
[000C] VAR_RESULT = getObjectOwner(873);
[0011] if (VAR_RESULT == 1) {
[0018]   cutscene([1]);
[001D]   startSound(50);
[001F]   animateCostume(1,255);
[0022]   delay(60);
[0026]   animateCostume(1,246);
[0029]   delay(60);
[002D]   Resource.loadCostume(66);
[0030]   ShakeOn();
[0036]   delay(30);
[003A]   drawObject(874,255,255);
[0041]   startSound(6);
[0043]   delay(10);
[0047]   drawObject(875,255,255);
[004E]   startSound(6);
[0050]   delay(30);
[0054]   ActorOps(1,[Costume(66)]);
[0059]   animateCostume(1,6);
[005C]   Local[0] = 1;
[0061]   breakHere();
[0062]   Local[0]++;
[0065]   unless (Local[0] > 20) goto 0061;
[006C]   delay(30);
[0070]   VAR_TIMER_NEXT = 20;
[0075]   setState(875,0);
[0079]   setState(874,1);
[007D]   startSound(6);
[007F]   breakHere();
[0080]   setState(874,0);
[0084]   startSound(6);
[0086]   VAR_TIMER_NEXT = 6;
[008B]   delay(60);
[008F]   ShakeOff();
[0095]   delay(60);
[0099]   stopSound(50);
[009B]   VAR_RESULT = getActorRoom(3);
[009F]   if (VAR_RESULT == 82) {
[00A6]     startScript(206,[3]);
[00AC]     startScript(206,[10]);
[00B2]     VAR_EGO = 3;
[00B7]     actorFollowCamera(VAR_EGO);
[00BA]     Var[100] = VAR_CAMERA_POS_X;
[00BF]     breakHere();
[00C0]     unless (VAR_CAMERA_POS_X == Var[100]) goto 00BA;
[00C7]     VAR_RESULT = getRandomNr(1);
[00CB]     if (VAR_RESULT) {
[00D0]       print(3,[Text("I could have gotten the Grail out of here.")]);
[00FE]       WaitForMessage();
[00FF]       print(10,[Text("Don't bet on it.")]);
[0113]     } else {
[0116]       print(3,[Text("Indiana.  You should have let me take the Grail.")]);
[014A]       WaitForMessage();
[014B]       print(10,[Text("I think his fate was in^")]);
[0167]       WaitForMessage();
[0168]       animateCostume(10,250);
[016B]       breakHere();
[016C]       breakHere();
[016D]       print(10,[Text("^other hands.")]);
[017E]     }
[017E]     WaitForMessage();
[017F]     if (Bit[1497]) {
[0184]       setBoxFlags(17,0);
[0187]       setBoxFlags(18,0);
[018A]     } else {
[018D]       setBoxFlags(6,0);
[0190]       setBoxFlags(7,0);
[0193]     }
[0193]     walkActorToObject(10,862);
[0197]     startScript(68,[3,138,111],R);
[01A3]     waitForActor(3);
[01A5]     Var[99] = 3;
[01AA]   } else {
[01AD]     panCameraTo(0);
[01B0]     Var[100] = VAR_CAMERA_POS_X;
[01B5]     breakHere();
[01B6]     unless (VAR_CAMERA_POS_X == Var[100]) goto 01B0;
[01BD]     VAR_EGO = 2;
[01C2]     actorFollowCamera(VAR_EGO);
[01C5]     Var[99] = 2;
[01CA]   }
[01CA]   Var[100] = VAR_CAMERA_POS_X;
[01CF]   breakHere();
[01D0]   unless (VAR_CAMERA_POS_X == Var[100]) goto 01CA;
[01D7]   endCutscene();
[01D8] } else {
[01DB]   cutscene([1]);
[01E0]   startScript(68,[1,138,111,244],R);
[01EF]   waitForActor(1);
[01F1]   Var[99] = 4;
[01F6]   Var[100] = VAR_CAMERA_POS_X;
[01FB]   breakHere();
[01FC]   unless (VAR_CAMERA_POS_X == Var[100]) goto 01F6;
[0203]   endCutscene();
[0204] }
[0204] if (Var[99]) {
[0209]   startScript(65,[]);
[020C] }
[020C] stopObjectCode();
END

Radiant

Very interesting.

So the second script basically goes like this.
Code: ags

if you leave the temple,
  if you're carrying the grail
    you die
    if Elsa is in the room
      var[99] = 3
    else
      var[99] = 2
    endif 
  else
     var[99] = 4
  endif
endif


Presumably another script sets var[99]=1 if you return the grail to the knight. Then the first script displays the endgame: (1) Just and noble thing, (2) Why didn't you listen, (3) Elsa says she could have taken the grail, (4) randomly either "quest for the sacred part" or the "steal his grail" line.

(1) Pick up the grail, give it to the knight; Indy and Elsa leave with Henry and Marcus.
(2) Pick up the grail, walk out of the temple, a chasm opens under you and you die.
(3) Pick up the grail, give it to Elsa; OR just wait and she'll pick it up herself; OR try to walk out and she'll also pick it up. A chasm opens under Elsa and she dies.
(3A) now you can walk out; Indy leaves with Henry and Marcus.
(3B) or you can pick up the grail with your whip and give it to the knight; Indy again leaves with Henry and Marcus.
(3C) or you can pick up the grail with your whip and walk out, and a chasm opens under you and you die.

This means that you actually can't steal the grail, and the line will randomly occur in situation 3A; the line is technically true because the grail is stolen, even if it's in the bowels of the earth now. That's what I wanted to find out, thanks for your help!

edmundito

#19
Here's the script where Indy gives the grail back to the knight:

Code: ags

Script# 203
[0000] cutscene([1]);
[0005] print(10,[Text("I give you my thanks." + wait() + "I see you indeed have the heart of a true Knight.")]);
[0051] WaitForMessage();
[0052] if (!Bit[1497]) {
[0057]   startScript(125,[69,100]);
[0060]   VAR_RESULT = getRandomNr(1);
[0064]   if (VAR_RESULT) {
[0069]     print(10,[Text("You should have seen the wreck this place was in" + newline() + "after the last guys left.")]);
[00B8]   } else {
[00BB]     print(10,[Text("Your nobility has kept the Grail safe, and this" + newline() + "Temple unspoiled." + wait() + "May you find your true path.")]);
[011F]   }
[011F]   setBoxFlags(6,0);
[0122]   setBoxFlags(7,0);
[0125] } else {
[0128]   startScript(125,[70,75]);
[0131]   VAR_RESULT = getRandomNr(1);
[0135]   if (VAR_RESULT) {
[013A]     print(10,[Text("Of course, I'll be picking this place up for years^")]);
[0171]   } else {
[0174]     print(10,[Text("But now you must go.  I fear I have a Herculean" + newline() + "task yet before me." + wait() + "Now where was that broom?")]);
[01D7]   }
[01D7]   setBoxFlags(17,0);
[01DA]   setBoxFlags(18,0);
[01DD] }
[01DD] WaitForMessage();
[01DE] walkActorToObject(10,862);
[01E2] waitForActor(10);
[01E4] putActorInRoom(10,0);
[01E7] setBoxFlags(6,128);
[01EA] setBoxFlags(7,128);
[01ED] setBoxFlags(17,128);
[01F0] setBoxFlags(18,128);
[01F3] if (!Bit[1497]) {
[01F8]   startScript(68,[1,518,113,245],R);
[0207]   startScript(68,[3,575,112,244],R);
[0216]   waitForActor(1);
[0218]   print(3,[Text("We could have taken the Grail from this place." + wait() + "It could have been ours, Indy.")]);
[026A]   WaitForMessage();
[026B]   startScript(68,[1,138,111,244],R);
[027A]   startScript(68,[3,163,118,244],R);
[0289]   VAR_RESULT = getRandomNr(1);
[028D]   if (VAR_RESULT) {
[0292]     print(1,[Text("Don't bet on it.")]);
[02A6]   } else {
[02A9]     print(1,[Text("That may be your game plan, Elsa, but" + newline() + "I won't play it that way.")]);
[02ED]   }
[02ED] } else {
[02F0]   startScript(68,[1,138,111,244],R);
[02FF] }
[02FF] waitForActor(1);
[0301] waitForActor(3);
[0303] Var[99] = 1;
[0308] Var[100] = VAR_CAMERA_POS_X;
[030D] breakHere();
[030E] unless (VAR_CAMERA_POS_X == Var[100]) goto 0308;
[0315] endCutscene();
[0316] if (Var[99]) {
[031B]   startScript(65,[Var[99]]);
[0321] }
[0321] stopObjectCode();
END


Sidenote: SCUMM script didn't look like this at all. This is ScummVM interpreting names for things based on what unreadble chunks of data were supposed to do (the variable names are lost, for example.). Ron Gilbert is the only person who described what it looked like in presentations and in this article: http://www.pagetable.com/?p=614

SMF spam blocked by CleanTalk