Community
SCI Programming => SCI Development Tools => Topic started by: Charles on June 12, 2016, 11:05:00 PM
-
So I've decompiled QFG1 EGA, and am going through trying to replace any proc# or localproc# with actual descriptive names (same for global variables, etc), and it's going fairly well. I'd say I'm learning a lot about SCI scripting in the process, but there are a couple of items/questions that have come up that I'm not sure how to interpret.
1) in script 968 (SmoothLooper) the doit method of the SmoothLooper class decompiles into this:
(method (doit &tmp [temp0 2])
CorruptFunction_CantDetermineCodeBounds
)
So clearly something unexpected is going on with the byte-code. I'm not really too keen on understanding the raw byte-code, but at the very least is there a way to force SCICompanion to try converting it to asm? Unless anybody has an other suggestion?
2) kernel_113 is called in several scripts (1, 255, 202, 32). Is this an undocumented kernel call? No clue what it is supposed to do.
3) DoSound. The documentation (http://scicompanion.com/Documentation/Kernels/DoSound.html?highlight=sndCHECK_DRIVER) says that DoSound(sndCHECK_DRIVER) only returns TRUE or FALSE, depending if the sound driver is installed, however there is code in QFG1 to the effect of (DoSound(sndCHECK_DRIVER) == 1), (DoSound(sndCHECK_DRIVER) <= 4), (DoSound(sndCHECK_DRIVER) > 4), implying it is more along the lines of the number of music channels present.
That's about all I have for now. Thanks for reading.
-
1) Companion currently determines the boundaries of a function by getting the start point (which is known). Then, it walks the code from that point, looking for a ret (return) opcode that is beyond the target of any branch instructions encountered yet. The problem is, the script in question is corrupt (using SV.exe, look at the bnt instruction just after code_01b7) - there is a branch that goes way off into space (clearly it must be in code that is never actually executed at runtime - I wonder how Sierra's compiler generated this byte code though). This confuses Companion, so it can't determine where the function ends (hence not even being able to fall back to asm). You can see a little message about this in the decompiler output window (ERROR: Invalid branch target).
Let me think what I can do about this. I can probably use the starts of other functions to at least know that that branch goes out of bounds. I maybe not be able to produce readable code, but at least it could fall back to asm.
2) That might be Joystick. From ScummVM (unimplemented): // Subfunction 12 sets/gets joystick repeat rate
3) It probably is. Much of the SCI0 documentation was copied from the SCI Studio docs. Indeed, ScummVM calls it "DoSoundGetPolyphony".
-
I got it to output some source code (attached).
The bad branch instruction got replaced by -17747 in the source code ($baad in hex). So it's not "correct", but that code must never get hit anyway (as it would crash)
-
So I've decompiled QFG1 EGA, and am going through trying to replace any proc# or localproc# with actual descriptive names (same for global variables, etc), and it's going fairly well.
That's a cool project!
Would you be sharing your results when you're done? We could all learn from your work.
2) kernel_113 is called in several scripts (1, 255, 202, 32). Is this an undocumented kernel call? No clue what it is supposed to do.
2) That might be Joystick. From ScummVM (unimplemented): // Subfunction 12 sets/gets joystick repeat rate
Actually, kernel 113 is "Intersections (https://github.com/scummvm/scummvm/blob/0a5a722b027303e29b486e146a580f7881ca2a2d/engines/sci/engine/kpathing.cpp#L849)":
Computes the nearest intersection point of a line segment and the polygon
set. Intersection points that are reached from the inside of a polygon
are ignored as are improper intersections which do not obstruct
visibility
Parameters: (PathfindingState *) s: The pathfinding state
(const Common::Point &) p, q: The line segment (p, q)
Returns : (int) PF_OK on success, PF_ERROR when no intersections were
found, PF_FATAL otherwise
(Common::Point) *ret: On success, the closest intersection point
-
I think that's for SCI1 - this is SCI0, where Joystick is 113 (the parameters also match the signature). Open the game in Scumm, go to the debugger and type "functions" and you'll see.
-
Sorry - you are correct!
-
I was just looking at the script from Hero's Quest and it seems to be different from that of QfG1EGA.
-
2) That might be Joystick. From ScummVM (unimplemented): // Subfunction 12 sets/gets joystick repeat rate
Oh yeah, that makes sense... they're setting the joystick repeat rate to 0 before disposing... so that'd make the 12 actually jsCALL_DRIVER.
I got it to output some source code (attached).
Oh, that's awesome! I've put that code in place.
I was just looking at the script from Hero's Quest and it seems to be different from that of QfG1EGA.
Yeah, I noticed the same thing. At least HQ v1.000 does. HQ v1.102 looks more or less identical to QFG1 v1.2.
I just assumed that's the same style as the earlier KQ4 and LSL2 scripts. I haven't looked at those myself. There's a bunch of differences in HQ v1.000. Like each script starts off with the line (version 2), Includes and Uses have quotes around the filenames, the script number declaration doesn't use the # sign, there are no temp variables created with a procedure (instead there's a var declaration at the start of applicable procedures, public is called exports and its index then name, instead of the other way around. Oh and it looks like {} are not used on embedded strings. Dunno if there are any other differences... or what it really means.
-
I just assumed that's the same style as the earlier KQ4 and LSL2 scripts. I haven't looked at those myself. There's a bunch of differences in HQ v1.000. Like each script starts off with the line (version 2), Includes and Uses have quotes around the filenames, the script number declaration doesn't use the # sign, there are no temp variables created with a procedure (instead there's a var declaration at the start of applicable procedures, public is called exports and its index then name, instead of the other way around. Oh and it looks like {} are not used on embedded strings. Dunno if there are any other differences... or what it really means.
None of this has anything to do with the game itself. It just means you have set the game language to "Sierra Script" as opposed to "SCI Studio" when decompiling (set in Game->Properties).
Things should default to Sierra Script, unless Companion thought you had been editing it in SCI Studio (I think if it detects a game.ini in your game folder that doesn't have an explicit language setting, it might do that).
Some relevant documentation links:
http://scicompanion.com/Documentation/scripts.html#sierra-script
http://scicompanion.com/Documentation/sci_compiler.html
-
Oh. That explains why the syntax is like a slightly distorted version to what I'm familiar with. Not sure on why it defaulted to that though. I copied the game.ini file from my QFG1-EGA decompile, so I could keep the filenames I'd settled on.
-
I might be able to be more aggressive about defaulting to Sierra script. I guess I was trying to avoid problems for people with existing games that didn't want to convert their stuff over to the new Sierra script syntax. Maybe if the language is "unspecified", then I can check and see if there is a Main.sc that is in the old syntax. If not, then I will assume Sierra script.
-
Let me know if you're interested in trying out a build of Companion that includes a bunch of decompiler fixes. I've been slowly plugging away at decompiler bugs - mainly the insidious ones that silently produce code that is incorrect. So that the "dream" of being able to "recompile all" and expect it to work perfectly is closer...
It's not quite there yet, but I've been able to decompile Laura Bow 1, and recompile all scripts (correcting a few errors that the decompiler can't possibly make sense of, like missing scripts), and it works well enough to to get through all the intro stuff, and walk around a few rooms and talk to people. I just tried QFG1, and there are still about 4 compile errors I get that I should be able to get rid of with a little more decompiler/compiler work.
-
Thank you. On another note, I would love to setup a repository of the source for official games that will compile without error. Perhaps a repository on the Wiki.
-
Great idea!
But the code better be kept in some kind of source control repository like git or mercurial, so people could work on it in a continuous way.
-
I use Bitbucket for my projects. It allows 5 users per account with unlimited repos. I would be happy to add a couple more users for this.
-
Why not GitHub?
-
Let me know if you're interested in trying out a build of Companion that includes a bunch of decompiler fixes. I've been slowly plugging away at decompiler bugs - mainly the insidious ones that silently produce code that is incorrect. So that the "dream" of being able to "recompile all" and expect it to work perfectly is closer...
It's not quite there yet, but I've been able to decompile Laura Bow 1, and recompile all scripts (correcting a few errors that the decompiler can't possibly make sense of, like missing scripts), and it works well enough to to get through all the intro stuff, and walk around a few rooms and talk to people. I just tried QFG1, and there are still about 4 compile errors I get that I should be able to get rid of with a little more decompiler/compiler work.
Yeah, I'd be interested in that. When you compile LB1, are you able to watch the Intro? When I tried removing the Copy Protection (and replacing it with QFG1-style "Piracy hurts" dialog) I suddenly wasn't able to view the Intro no matter if I clicked yes or no.
Speaking of compiling QFG1, I hadn't done that before... I was just happily replacing global variable names and public procedures. When I tried to do that yesterday, I wasn't able to compile at all, and it felt like the biggest reason was because the new names had become decync'd with the .sco file. Actually, speaking of your Manage Decompilation window says:
Decompiling a script will also generate a .sco file. The .sco file tracks procedure and variable names which are not present in the compiled script. By default they are given names such as "local4"
You may edit the names to make them more meaningful, and they will be picked up the next time you decompile the script.
How do you edit the names? Or does that mean I just edit the .sc file and if I click decompile again, it will read in those names and use them? That seems counter-intuitive to "decompile" when I've already got source code... Any way to edit the sco file directly? i.e. is there any documentation on the format?
I'd written q quick'n'dirty program to the more tedious editing... things like renaming script files and updating all use references to it... or doing global search/replace (with warnings if the new string already exists), or adding in include keys.sh and include game.sh if they're not already there.
That's a cool project!
Would you be sharing your results when you're done? We could all learn from your work.
Yeah, I don't really have a problem sharing when I'm done (or as I go along). I'm doing it all in a personal git server now (GitBlitGO, it's super easy to get up and running), so I can probably open up access to that to a few more people. I think it might be better not putting it on something like GitHub though... it is technically copyrighted material. I mean, sharing among a small community of people who I'm pretty sure already legally own copies of the games anyway is one thing...
Y'know, speaking of SCICompanion updates, there's a couple fixes (some relatively simple, some maybe not so) for issues I've come across that would greatly improve my workflow... the three big ones:
1) When doing a Compile All, showing the file that is causing an error, not just the line/col nums,
2) being able to middle-click (or wheel-click) on a tab to close it... or at the very least including a Close All option under the Window menu, and
3) having an option to compile as external patch files, rather than automatically rebuilding into resource.000... this one really hurts my source control... I'll compile a script to check I've not made any errors, and it will modify my resource.000 (which is about 2-3 MB in these early SCI0 games, but that whole 2MB gets added to my source control... In theory, it should only add the difference, but I've found difference checking to be spotty with binary files.
-
Yeah, I'd be interested in that. When you compile LB1, are you able to watch the Intro? When I tried removing the Copy Protection (and replacing it with QFG1-style "Piracy hurts" dialog) I suddenly wasn't able to view the Intro no matter if I clicked yes or no.
Yes, I'm able to now. That was caused by a decompilation bug in proc255_0 (Print).
How do you edit the names? Or does that mean I just edit the .sc file and if I click decompile again, it will read in those names and use them? That seems counter-intuitive to "decompile" when I've already got source code... Any way to edit the sco file directly? i.e. is there any documentation on the format?
You edit them from within the decompiler dialog in the box that says "SCO". It doesn't pick anything up from the script files - that would be counter-intuitive. You need to do everything from the decompile dialog. Again, this is in the documentation:
http://scicompanion.com/Documentation/decompiler.html
(I do suggest you read the docs - some of the questions you've asked here before are answered in there)
Brian documented his .sco format. Should be on the wiki somewhere. At any rate, it doesn't really matter, .sco files are generated when you build too. So if you build things in the right order, there shouldn't be any issues. For example, change a public procedure name in in foobar.sc, and then foobar.sco will get that name. Then other scripts that depend on foobar will now compile correctly (assuming they're using the new name). I do have some script dependency tracking stuff in SCI Companion, but I forget if it's linked to "compile all". Not to mention, it won't work if the scripts don't parse because of syntax errors.
2) being able to middle-click (or wheel-click) on a tab to close it... or at the very least including a Close All option under the Window menu, and
Does clicking on the red X not work or something? Or you just want a bigger hit target? (Hmm, I just noticed VS does this... never used that functionality before).
3) having an option to compile as external patch files, rather than automatically rebuilding into resource.000... this one really hurts my source control...
I'd really like this too :P. I wish I'd broken from Brian's compile model earlier and had a chance to do this correctly. It seems like a big change, but I'll look into it, since I'm not the only one frustrated by it. Most of the groundwork is there to make this happen, so maybe it's not as bad as I think to make this change.
-
Shouldn't it be just the sc/sco files and not the compiled resource package for your source control?
-
Why not GitHub?
Last I looked GitHub did not have a free account and my Bitbucket account allows for unlimited repos.
-
I used github for SCI Companion and I didn't pay anything (I think it's free for open source stuff only?). And AFAIK there are no limits on number of repos.
-
Again, this is in the documentation:
http://scicompanion.com/Documentation/decompiler.html
(I do suggest you read the docs - some of the questions you've asked here before are answered in there)
I'll give that another read. I'd thought I'd read through most of the docs before, but that was before I'd started diving in and I could clearly use a refresher.
You edit them from within the decompiler dialog in the box that says "SCO".
I swear I'd tried that, but I'm trying it now, and I must not have been patient enough with it before. I tried selecting the line items, I tried double-clicking on it, but I never tried selecting it then single-clicking it (or pressing F2). And now that I do, I see what appears to be a slight bug where you can also edit the label "Public procedures" and "Variables"
2) being able to middle-click (or wheel-click) on a tab to close it... or at the very least including a Close All option under the Window menu, and
Does clicking on the red X not work or something? Or you just want a bigger hit target? (Hmm, I just noticed VS does this... never used that functionality before).
do you mean the x on the tab itself? If I have a dozen or more scripts open and I want to close them all, I generally want to hover over one of the tabs, and middle-click rapid fire to close them all. Because the names of the scripts are variable length, the x is not always in the same spot. But I hadn't thought until now of just using the MDI Child x in the upper right window. So nevermind about the wheel-click request.
Shouldn't it be just the sc/sco files and not the compiled resource package for your source control?
Yup, absolutely. Hindsight's 20/20.
-
I can now decompiler QFG2 without error, and recompile all scripts and it appears to run (just walked around a few screens). :D
QFG1 too.
-
Epic.
And you said you didn't want to mess with the decompiler :3
-
I can now decompiler QFG2 without error, and recompile all scripts and it appears to run (just walked around a few screens). :D
QFG1 too.
You can now decompile the SmoothLooper function and the --UNKNOWN-PROP-NAME-- in Feature.sc? That's awesome!
-
I just signed up for GitHub. They seem to not want to tell you of the free options until after you sign up. I would really like to establish repos for the decompiled scripts of the official games. As I said I can set them up on my Bitbucket account, which has unlimited repos, and allows them to be private. Its drawback is the number of members is limited to just 5 per account. Do we need more than 5? GitHub has unlimited repos and members, but is higher profile and with no private repos with their free accounts Charles may have a point about needing a less conspicuous repository.
What do others say about where to host the repos?
-
You can now decompile the SmoothLooper function and the --UNKNOWN-PROP-NAME-- in Feature.sc? That's awesome!
Oops, there's an asterisk beside my claims :P
UNKNOWN-PROP-NAME in canBeHere had to be fixed manually. SmoothLooper is fine though. And I think in both games there were missing scripts that were related to debugging, or development builds. Some of the game scripts reference them, so you have to comment those lines out. But all of that only takes 30 seconds or so. I'm not sure if I can do that automatically. I mean, I probably can, but it might be better to leave those errors in so it's obvious what's "missing". I'm not sure.
I'll probably add a list of tips on decompiling to the documentation that go over the common remaining errors like those and how to fix them/comment them out. Ideally the only errors will be corrupt scripts (like the UNKNOWN-PROP-NAME issue) and missing scripts - i.e. things that are beyond the capability of the decompiler, since they're essentially impossible to fix.
The real big deal is that the decompiler is getting decent enough that - as of now - I don't know of any incorrect code it generates (yes, it still falls back to asm for around 2% of the code). I'm sure there is some, but it's much better than before, where I had to warn people who were modding *just* to recompile the scripts they were changing, not all the scripts - or else something would break.
-
Out of curiosity, what version of QfG2 is this?
-
Great news Phil!
About the remaining decompilation errors:
I think you should generate special code (inline assembly?) that would tell the compiler to ignore the error and generate as-is.
Same for missing (debug) scripts: the compiler should ignore it. The interpreter tries to load the scripts dynamically, so the user can drop-in the missing scripts and the game would use them.
If you force the user to comment out these references before compilation, this functionality would be gone. You can perhaps make it a compilation warning.
The ideal decompiler/compiler combo would allow to decompile and recompile the whole game and generate identical bytecode with no errors and human intervention.
-
Out of curiosity, what version of QfG2 is this?
1.102
About the remaining decompilation errors:
I think you should generate special code (inline assembly?) that would tell the compiler to ignore the error and generate as-is.
Same for missing (debug) scripts: the compiler should ignore it. The interpreter tries to load the scripts dynamically, so the user can drop-in the missing scripts and the game would use them.
If you force the user to comment out these references before compilation, this functionality would be gone. You can perhaps make it a compilation warning.
I agree. I was trying to figure out how this code could be left in without producing an error (and without introducing new syntax). An asm block might be the way.
-
The one version that I am missing. I guess this is one thing that any repository would have to take into account is different game versions, not that we should expect a decompilation of every version, but for any collaborative effort everyone would need to be working with the same version.
-
I agree. I was trying to figure out how this code could be left in without producing an error (and without introducing new syntax). An asm block might be the way.
I've decided to go with special markup that lets you call a missing export without causing a compile error:
(__proc_341_0 "hello") ; calls export 0 in script 341
Easier to implement than having the decompiler stick an asm block in there. I actually already do something similar with missing kernels (i.e. when I can't find a kernel name to map it to), so in some decompiles you might see:
(kernel_121 "blahblah") ; calls kernel 121
-
Sound great!
-
Hey Charles,
I found 2 scripts from QfG1. You might want to use them for reverse engineering the game's scripts.
CHARSAVE.SC
;charsave.sc Hero's Quest...save character stats.
(script# CHARSAVE)
(public
CharSave 0
)
;; Bits in svMiscEquip
(define SWORD_BIT 1)
(define CHAIN_BIT 2)
(define PICK_BIT 4)
(define TOOL_BIT 8)
(define MIRROR_BIT 16)
(define BABA_BIT 32)
(define SCORE_BIT 64)
(define EXTRA_DATA 18) ; Data items other than stats and name
(define CHECK_DATA 10) ; Data items that are in check sums
(local
;; local data for saving hero stats for next game
;;;;;;;;;;;;;;;;;;start;;;;;;;;;;;;;;;;;;
statsKey = $53 ;;;;;;;;order dependent variables;;;;;;;;
svCharType ;;;;;;;;order dependent variables;;;;;;;;
svHighGold ;;;;;;;;order dependent variables;;;;;;;;
svLowGold ;;;;;;;;order dependent variables;;;;;;;;
svScore ;;;;;;;;order dependent variables;;;;;;;;
svMiscEquip ;;;;;;;;order dependent variables;;;;;;;;
[codedStats NUMSTATS] ;;;;;;;;order dependent variables;;;;;;;;
svDaggers ;;;;;;;;order dependent variables;;;;;;;;
svHealing ;;;;;;;;order dependent variables;;;;;;;;
svMana ;;;;;;;;order dependent variables;;;;;;;;
svStamina ;;;;;;;;order dependent variables;;;;;;;;
svGhostOil ;;;;;;;;order dependent variables;;;;;;;;
bogus0 = $79 ;;;;;;;;order dependent variables;;;;;;;;
bogus1 = $86 ;;;;;;;;order dependent variables;;;;;;;;
checkSum1 ;;;;;;;;order dependent variables;;;;;;;;
checkSum2 ;;;;;;;;order dependent variables;;;;;;;;
bogus2 = $43 ;;;;;;;;order dependent variables;;;;;;;;
bogus3 = $88 ;;;;;;;;order dependent variables;;;;;;;;
bogus4 = $ad ;;;;;;;;order dependent variables;;;;;;;;
bogus5 = $f0 ;;;;;;;;order dependent variables;;;;;;;;
checkSumKey = $ce ;;;;;;;;order dependent variables;;;;;;;;
;;;;;;;;;;;;;;;;;;;end;;;;;;;;;;;;;;;;;;;
check1
check2
[YNSTR 5]
[heroFileName 16]
[bigStr 400]
hasSaved ;TRUE if hero saved
[str 40]
)
(enum ;states of saveHero Script
askSave
getInfoFileName
getInfoFileName2
openFile
writeHeroInfo
writeComplete
tryAgain
badAnswer
saveDone
)
;(procedure
; makeChar
; makeZero
; restoreHero
; convWord
; convByte
;)
;
;(procedure (makeZero &tmp whichSkill)
; (HighPrint "make Zero")
; (for ((= whichSkill 0))
; (< whichSkill NUMSTATS)
; ((++ whichSkill))
; (= [egoStats whichSkill] 0)
; )
; (= [invNum iGold] (= [invNum iSilver] (= score 0)))
; (ego use: iSword)
; (ego use: iChainMail)
; (ego use: iLockPick)
; (ego use: iThiefKit)
; (ego use: iMagicMirror)
; (Bclr fBabaFrog)
; (= [invNum iDagger] 0)
; (= [invNum iHealingPotion] 0)
; (= [invNum iManaPotion] 0)
; (= [invNum iStaminaPotion] 0)
; (= [invNum iGhostOil] 0)
; (= score 0)
; (= heroType 0)
; (StrCpy @userName {xxxxxxxxxxy})
; (for ((= whichSkill 0))
; (< whichSkill (+ NUMSTATS EXTRA_DATA))
; ((++ whichSkill))
; (= [statsKey (+ whichSkill 1)] 0)
; )
; (HighPrint "char Zeroed")
; (return)
;)
;
;(procedure (makeChar &tmp whichSkill)
; (HighPrint "make Char")
; (for ((= whichSkill 0))
; (< whichSkill NUMSTATS)
; ((++ whichSkill))
; (= [egoStats whichSkill] whichSkill)
; )
; (= [invNum iDagger] 1)
; (= [invNum iHealingPotion] 2)
; (= [invNum iManaPotion] 3)
; (= [invNum iStaminaPotion] 4)
; (= [invNum iGhostOil] 5)
; (= score 432)
; (HighPrint "Char made")
; (return)
;)
;
;(procedure (restoreHero &tmp whichSkill)
; (HighPrint "restore Hero")
; (if (not (heroinfo open: fRead))
; (HighPrint "Failure in opening file")
; (return)
; )
; (heroinfo readString: @userName 52)
; (heroinfo readString: @bigStr 90)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; (for ((= whichSkill 0))
; (< whichSkill (+ NUMSTATS EXTRA_DATA))
; ((++ whichSkill))
;
; (= [statsKey (+ whichSkill 1)] (convWord [bigStr whichSkill]))
;
; )
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; (for ((= whichSkill (+ NUMSTATS EXTRA_DATA)))
; (< 0 whichSkill)
; ((-- whichSkill))
;
; (^= [statsKey whichSkill] (& [statsKey (- whichSkill 1)] 127))
;
; )
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; (= check1 checkSumKey)
; (for ((= whichSkill 0))
; (< whichSkill (+ NUMSTATS CHECK_DATA))
; ((+= whichSkill 2))
; (= [statsKey (+ whichSkill 1)] (& [statsKey (+ whichSkill 1)] 127))
; (+= check1 [statsKey (+ whichSkill 1)])
; )
; (= check2 0)
; (for ((= whichSkill 1))
; (< whichSkill (+ NUMSTATS CHECK_DATA))
; ((+= whichSkill 2))
; (= [statsKey (+ whichSkill 1)] (& [statsKey (+ whichSkill 1)] 127))
; (+= check2 [statsKey (+ whichSkill 1)])
; )
; (&= check1 127)
; (&= check2 127)
; (if (or (!= check1 checkSum1) (!= check2 checkSum2))
; (HighPrint "CHECKSUM ERROR")
; )
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; (for ((= whichSkill 0))
; (< whichSkill NUMSTATS)
; ((++ whichSkill))
; (= [egoStats whichSkill] [codedStats whichSkill])
; )
; (= [invNum iDagger] svDaggers)
; (= [invNum iHealingPotion] svHealing)
; (= [invNum iManaPotion] svMana)
; (= [invNum iStaminaPotion] svStamina)
; (= [invNum iGhostOil] svGhostOil)
; (= [invNum iGold] (+ (* svHighGold 100) svLowGold))
; (= score (+ svScore 128))
; (if (& svMiscEquip SCORE_BIT) (+= score 256))
; (if (& svMiscEquip SWORD_BIT) (= [invNum iSword] 1))
; (if (& svMiscEquip CHAIN_BIT) (= [invNum iChainMail] 1))
; (if (& svMiscEquip PICK_BIT) (= [invNum iLockPick] 1))
; (if (& svMiscEquip TOOL_BIT) (= [invNum iThiefKit] 1))
; (if (& svMiscEquip MIRROR_BIT) (= [invNum iMagicMirror] 1))
; (HighPrint "Hero restored")
; (return)
;)
;
;(procedure (convWord ascii)
; (return (+ (convByte (>> ascii 8)) (* (convByte (& ascii 255)) 16)))
;)
;
;(procedure (convByte ascii)
; (cond
; ((== ascii 32)
; (return 0)
; )
; ((<= 48 ascii 57)
; (return (- ascii 48))
; )
; (else
; (return (- ascii 87))
; )
; )
;)
(instance CharSave of Room
(properties
picture pBlueSkyForCarpet
horizon 0
style IRISOUT
)
(method (dispose)
(StatusLine code: dftStatusCode)
(super dispose:)
)
(method (init)
(StatusLine
code: endStatus,
enable:)
(super init: &rest)
(cSound stop:)
; don't let'm control anything!
(User canControl: FALSE, canInput: FALSE)
;; (User canControl: FALSE, canInput: TRUE)
(self setScript: saveHero)
)
; (method (handleEvent event)
; (if (Said 'quit')
; (= quit TRUE)
; )
; (if (Said 'make/zero')
; (makeZero)
; )
; (if (Said 'make/hero')
; (makeChar)
; )
; (if (and (Said 'restore/hero') hasSaved)
; (restoreHero)
; )
; )
)
(instance heroinfo of File
(properties
name {hq1_hero.sav})
)
(instance saveHero of Script
(method (changeState newState &tmp whichSkill oldGold)
(switch (= state newState)
(askSave
; (makeChar)
(Format @heroFileName "a:hq1\_hero.sav")
(if (>= score 500)
(HighPrint "CONGRATULATIONS!!__You have successfully
completed \"Hero's Quest I:__So You Want To Be
A Hero\" with the maximum possible score, 500 of 500!!_
We welcome you to the ranks of the few, the proud,
the True Heroes!")
else
(HighPrint (Format @bigStr "Congratulations!__You have
successfully completed \"Hero's Quest I:__So You
Want To Be A Hero.\"__Your final score was %d of
500 possible Puzzle Points." score))
)
(HighPrint "If you have not already done so,
we encourage you to play \"Hero's Quest I\" again
with the other two character types; many of the
puzzles are different, or have alternate solutions.")
(HighPrint "In the meantime, you are already a winner!__Please
insert a writeable disk in your floppy drive to
save your winning Hero for use in\n
\"Hero's Quest II:__Trial By Fire.\"")
(self cue:)
)
(getInfoFileName
(= cycles 2)
)
(getInfoFileName2
(if (GetInput @heroFileName 30
{Disk file in which to save your Hero.})
(heroinfo name: @heroFileName)
(= cycles 2)
else
(self changeState: tryAgain)
)
)
(openFile
(if (heroinfo open: fTrunc)
(heroinfo close:)
(= seconds 2)
else
(HighPrint (Format @bigStr "Could not create file -- %s."
(heroinfo name?)))
(self changeState: tryAgain)
)
)
(writeHeroInfo
(if (not (heroinfo open: fAppend))
(self changeState: tryAgain)
(return)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(for ((= whichSkill 0))
(< whichSkill NUMSTATS)
((++ whichSkill))
(= [codedStats whichSkill] [egoStats whichSkill])
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(= oldGold (+ [invNum iGold] (/ [invNum iSilver] 10)))
(= svCharType heroType)
(= svHighGold ( / oldGold 100))
(= svLowGold (mod oldGold 100))
(= svScore score)
(= svMiscEquip 0)
(if (ego has: iSword) (|= svMiscEquip SWORD_BIT))
(if (ego has: iChainMail) (|= svMiscEquip CHAIN_BIT))
(if (ego has: iLockPick) (|= svMiscEquip PICK_BIT))
(if (ego has: iThiefKit) (|= svMiscEquip TOOL_BIT))
(if (ego has: iMagicMirror) (|= svMiscEquip MIRROR_BIT))
(if (Btst fBabaFrog) (|= svMiscEquip BABA_BIT))
(if (< 255 score) (|= svMiscEquip SCORE_BIT))
(= svDaggers [invNum iDagger])
; (= svHealing [invNum iHealingPotion])
; (= svMana [invNum iManaPotion])
; (= svStamina [invNum iStaminaPotion])
(= svGhostOil [invNum iGhostOil])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(= checkSum1 checkSumKey)
(for ((= whichSkill 0))
(< whichSkill (+ NUMSTATS CHECK_DATA))
((+= whichSkill 2))
(= [statsKey (+ whichSkill 1)] (& [statsKey (+ whichSkill 1)] 127))
(+= checkSum1 [statsKey (+ whichSkill 1)])
)
(= checkSum2 0)
(for ((= whichSkill 1))
(< whichSkill (+ NUMSTATS CHECK_DATA))
((+= whichSkill 2))
(= [statsKey (+ whichSkill 1)] (& [statsKey (+ whichSkill 1)] 127))
(+= checkSum2 [statsKey (+ whichSkill 1)])
)
(&= checkSum1 127)
(&= checkSum2 127)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(for ((= whichSkill 0))
(< whichSkill (+ NUMSTATS EXTRA_DATA))
((++ whichSkill))
(= [statsKey (+ whichSkill 1)] (& [statsKey (+ whichSkill 1)] 127))
(^= [statsKey (+ whichSkill 1)] [statsKey whichSkill])
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(heroinfo writeString: @userName)
(heroinfo writeString: {\n})
(for ((= whichSkill 1))
( < whichSkill (+ NUMSTATS EXTRA_DATA 1))
((++ whichSkill))
(Format @bigStr "%2x" [statsKey whichSkill])
(heroinfo writeString: @bigStr)
)
(heroinfo writeString: {\n})
(heroinfo close:)
(= seconds 2)
)
(writeComplete
(HighPrint "The save character file has been created.__Save
this disk for use with \"Hero's Quest II:__Trial By Fire\"
from Sierra On-Line.")
(= hasSaved TRUE)
(= cycles 2)
)
(tryAgain
(Format @YNSTR "n")
(if (GetInput @YNSTR 2
{If you wish to try saving your character again,
type \"y\", then ENTER.__Otherwise type \"n\",
then ENTER.}
)
(if (StrCmp @YNSTR {y})
(self changeState: saveDone)
else
(= bogus0 $79)
(= bogus1 $86)
(= bogus2 $43)
(= bogus3 $88)
(= bogus4 $ad)
(= bogus5 $f0)
(self changeState: getInfoFileName)
)
else
(= cycles 2)
)
)
(badAnswer
(HighPrint "Please answer \"y\" or \"n\".")
(self changeState: tryAgain)
)
(saveDone
(HighPrint "Thank you for playing \"Hero's Quest I:__So You
Want To Be A Hero,\" and congratulations again on
winning.__We'll see you again soon in\n
\"Hero's Quest II:__Trial By Fire.\"")
(= quit TRUE)
)
)
)
)
(instance endStatus of Code
(method (doit strg)
(Format strg "___Wow!__You're Really A Hero!__[score %d of 500]" score)
)
)
MAZEBUG.SC
; MazeBug class to handle behavior of bugs in wizard's game (Mages' Mazes)
(script# MAZEBUG)
(include wizgame.sh)
(procedure
setDeltaX
setDeltaY
)
(class MazeBug kindof Actor
(properties
xStep 2
yStep 2
startX 0 ; Where to come back after dying
startY 0
deadTime 0 ; How long before we resurrect
otherBug 0 ; I.D. of opponent
form smallBug ; Which type (size) of creature
smarts 5 ; How far bug thinks/looks ahead
path 0 ; Which path we're following
)
(methods
highPri ; Switch to higher priority
lowPri ; Switch to lower priority
changeForm ; Trigger changes to next size/shape
fixState ; Set illegalBits and such based on priority/size
die ; Fall into the chasm and start game over
nearRock ; Are we close to a rock?
nearBridge ; Are we close to a bridge?
nearLadder ; Are we close to a ladder?
)
(method (init)
(super init: &rest)
(self
setPri: LowPri,
fixState:
)
)
(method (highPri)
(self
setPri: HighPri,
fixState:
)
)
(method (lowPri)
(self
setPri: LowPri,
fixState:
)
)
(method (changeForm &tmp newLoop)
(= newLoop (+ loop 1))
(if (> (++ form) largeBug)
( = form smallBug)
(-= newLoop 3)
)
(self
setLoop: newLoop,
fixState:
)
(if (and (!= form mediumBug) (== (self onControl: origin) cGREEN))
(self die:) ; Changing form while on a ladder
)
)
(method (fixState) ; Account for current creature size
(= illegalBits cWHITE)
(if (<= priority LowPri)
( |= illegalBits cYELLOW) ; On lower path
else
( |= illegalBits cLMAGENTA) ; On upper path
)
(switch form
(smallBug
( |= illegalBits cGREEN) ; Too small for ladders
( = moveSpeed (= cycleSpeed 0))
)
(mediumBug
; Too big to enter tunnels
;; ( |= illegalBits (| cMAGENTA cLMAGENTA))
( |= illegalBits cLMAGENTA)
( = moveSpeed (= cycleSpeed 1))
)
(largeBug
; Too big to enter tunnels or climb ladders (clumsy)
;; ( |= illegalBits ( | cMAGENTA cLMAGENTA cGREEN))
( |= illegalBits ( | cLMAGENTA cGREEN))
( = moveSpeed (= cycleSpeed 2))
)
)
)
(method (doit &tmp whichControl)
(cond
(deadTime
(if (not (-- deadTime)) ; Time to be resurrected
(self
posn: startX startY,
setLoop: (- loop (- form smallBug)),
form: smallBug,
lowPri:,
fixState:
)
)
)
(else
(super doit:)
)
)
)
(method (canBeHere &tmp canWe ctrls)
(if (= canWe (super canBeHere:))
(= ctrls (self onControl: 0))
(if (or
(and (& ctrls cLRED) (self nearRock: 25))
(and (& ctrls cLCYAN) (not (self nearBridge: 25)))
(and (& ctrls cGREEN) (not (self nearLadder: 25)))
)
(= canWe FALSE)
)
)
(return canWe)
)
(method (die)
(= deadTime 20) ; Disappear for 20 cycles
)
(method (nearRock dist) ; Are we close to a rock?
(return FALSE) ; Implemented in instance
)
(method (nearBridge dist) ; Are we close to a bridge?
(return TRUE) ; Implemented in instance
)
(method (nearLadder dist) ; Are we close to a ladder?
(return TRUE) ; Implemented in instance
)
)
(class MazeMove kindof Motion ; Motion class for bugs in maze
(properties
client 0 ; Actor we are controlling
caller 0 ; Object to cue when complete/blocked
x 0 ; Eventual destination
y 0
b-moveCnt 0 ; Iterations of doit to skip
curDir mazeSW ; Which way we're going (compass direction)
prevDir mazeSW ; Which way we last went (compass direction)
ditherTime 0 ; How long we should stand and dither
completed 0 ; Set to TRUE when move is complete
altMove 0 ; Kludge to make diagonal moves close to correct
prevControl 0 ; Which control color we were on last doit cycle
)
(methods
calcDir ; Calculate compass direction from angle
doMove ; Do next step of move
tryStep ; Try to take a step
chooseRoute ; Find best path for next move(s)
checkRoute ; See if specified route works
setHeading ; Set client's heading based on compass direction
)
;; Initialize mover -- do *not* do a (super init:)
(method (init actor xTo yTo toCall &tmp cy theAngle)
(if (>= argc 1) (= client actor)
(if (>= argc 2) (= x xTo)
(if (>= argc 3) (= y yTo)
(if (>= argc 4) (= caller toCall)
)
)
)
)
;Set actor's heading.
(client heading: (= theAngle (GetAngle (client x?) (client y?) x y)))
(= prevDir (= curDir (self calcDir: theAngle)))
(= b-moveCnt 0)
(if (= cy (client cycler?)) ; Keep cycler in synch with mover
(cy cycleCnt: 0)
)
)
(method (calcDir theHeading)
(return
(cond
(HEADING_N mazeN)
(HEADING_NE mazeNE)
(HEADING_E mazeE)
(HEADING_SE mazeSE)
(HEADING_S mazeS)
(HEADING_SW mazeSW)
(HEADING_W mazeW)
(HEADING_NW mazeNW)
)
)
)
;; Do next step -- do *not* do a (super doit:)
(method (doit &tmp clForm thisControl)
(if ditherTime
(-- ditherTime)
(return)
)
(if (> (++ b-moveCnt) (client moveSpeed?))
(= b-moveCnt 0)
(= clForm (client form?))
(= thisControl (client onControl: origin))
(if (and (!= thisControl prevControl) (< (client priority?) LowPri))
(client setPri: LowPri) ; (Maybe) just left a tunnel
)
(switch thisControl
(cGREY
(client lowPri:)
)
(cLBLUE
(client highPri:)
)
(cGREEN ; On a ladder -- get off quickly
(if (not (client nearLadder: 25))
(client die:) ; Somebody yanked away our ladder
)
)
(cLCYAN ; On a bridge, or where one should be
(if (not (client nearBridge: 25))
(client die:) ; Fall into the chasm
)
)
(cBROWN ; At an intersection -- choose best path
(self chooseRoute: FALSE)
)
(cLMAGENTA
(if (== (client priority?) LowPri)
(client setPri: (- LowPri 2))
)
)
(cMAGENTA
(if (== (client priority?) LowPri)
(client setPri: (- LowPri 2))
)
)
)
(= prevControl thisControl)
(self doMove:) ; Make next move (if we can)
)
;If our client is where he should be, we terminate.
(if (and (<= (Abs (- x (client x?))) 2) (<= (Abs (- y (client y?))) 2))
(self moveDone:)
(return)
)
)
; Actually do next step of motion
(method (doMove &tmp oldX oldY DX DY newDir newAngle mSpeed)
(if (== (Random 1 10) 7)
(self setHeading: (= newDir (Random 0 7))) ; Monkey wrench
)
(= oldX (client x?))
(= oldY (client y?))
(if (self tryStep: oldX oldY curDir)
(= prevDir curDir) ; Don't care about previous bump anymore
else
(self chooseRoute: TRUE) ; Blocked -- get a new route
(= mSpeed (client moveSpeed?))
(= ditherTime (+ 1 mSpeed mSpeed)) ; Dither for a bit
(client
signal: (| (client signal?) blocked),
forceUpd:
)
(return FALSE)
)
(client forceUpd:)
(return TRUE)
)
(method (tryStep oldX oldY theDir &tmp DX DY)
(= DX (client xStep?))
(= DY (client yStep?))
(if (== (++ altMove) 2)
(= altMove 0)
(if (& curDir 1) ; Moving at a diagonal
(<<= DX 1)
(<<= DY 1)
)
)
(= DX (setDeltaX theDir DX))
(= DY (setDeltaY theDir DY))
(client
x: (+ oldX DX),
y: (+ oldY DY)
)
(BaseSetter client)
(if (client canBeHere:)
(return TRUE)
else
(if (== (++ altMove) 2)
(= altMove 0)
)
(client
x: oldX,
y: oldY
)
(BaseSetter client)
(client
signal: (| (client signal?) blocked),
forceUpd:
)
(return FALSE)
)
)
(method (chooseRoute wasBlocked
&tmp theAngle newDir incr aDir sm best cur bestDir other forms dist)
(= newDir curDir)
(if (== (Random 1 3) 2)
(= theAngle (GetAngle (client x?) (client y?) x y))
(= newDir (self calcDir: theAngle))
)
(= other (client otherBug?))
(= dist (client distanceTo: other))
(if (< dist ChaseRange)
(= forms (- (client form?) (other form?)))
(cond
((== forms -1) ; Other bug wants to eat us -- run away!
(= theAngle
(GetAngle (other x?) (other y?) (client x?) (client y?)))
(= newDir (self calcDir: theAngle))
)
((and (< dist 12)
(== forms 1)
(== (client priority?) (other priority?))
)
; Yum -- we caught dinner!
(other die:)
)
; Oh, what a cute/tasty little bug -- let's chase it!
; (if merely "cute", only a 1 in 4 of chasing it)
((or (== forms 1) (and (== forms 0) (not (Random 0 3))))
(= theAngle
(GetAngle (client x?) (client y?) (other x?) (other y?)))
(= newDir (self calcDir: theAngle))
)
)
)
;; Check routes, preferring one selected above. If none of them
;; work, stick with the pre-chosen one.
;; (= sm (client smarts?))
(= bestDir newDir)
(for ((= incr (= best 0))) (<= incr 4) ((++ incr))
(= cur (self checkRoute: (= aDir (mod (+ newDir incr) 8))))
(if (== aDir (mod (+ curDir 4) 8))
(-= cur 4) ; Try not to reverse directions
)
(if (> cur best)
(= best cur)
(= bestDir aDir)
)
(= cur (self checkRoute: (= aDir (mod (- (+ newDir 8) incr) 8))))
(if (== aDir (mod (+ curDir 4) 8))
(-= cur 4) ; Try not to reverse directions
)
(if (> cur best)
(= best cur)
(= bestDir aDir)
)
)
(= newDir bestDir)
(while (and wasBlocked (== curDir newDir))
(= newDir (Random 0 7))
)
(self setHeading: newDir)
(client forceUpd:)
(return curDir)
)
;; See if endpoint of potential route is legal place for creature to be
;; (look-ahead distance determined by creature's smarts).
(method (checkRoute theDir &tmp oldX oldY curX curY sm index)
(= curX (= oldX (client x?)))
(= curY (= oldY (client y?)))
(= sm (/ (client smarts?) 2))
;; (= sm (- 11 (client smarts?))) ; Shorter looks are smarter (??)
(for ((= index 0)) (< index sm) ((++ index))
(if (not (self tryStep: curX curY theDir))
(break)
)
)
(client
x: oldX,
y: oldY
)
(BaseSetter client)
(return index)
)
(method (setHeading newHeading)
(= prevDir curDir)
(= curDir newHeading)
(client heading:
(switch newHeading
(mazeN 0)
(mazeNE 45)
(mazeE 90)
(mazeSE 135)
(mazeS 180)
(mazeSW 225)
(mazeW 270)
(mazeNW 315)
)
)
)
)
(procedure (setDeltaX theDir DX)
(switch theDir
(mazeN (= DX 0))
(mazeS (= DX 0))
(mazeSW (= DX (- DX)))
(mazeW (= DX (- DX)))
(mazeNW (= DX (- DX)))
)
(return DX)
)
(procedure (setDeltaY theDir DY)
(switch theDir
(mazeN (= DY (- DY)))
(mazeNE (= DY (- DY)))
(mazeE (= DY 0))
(mazeW (= DY 0))
(mazeNW (= DY (- DY)))
)
(return DY)
)
-
I found 2 scripts from QfG1. You might want to use them for reverse engineering the game's scripts.
I knew about the first one... the second one is interesting. It abusesdoes some creative things with the animation system.
-
Oooh, it's like Christmas. Those are both really helpful samples. The contents of both I'd already deciphered, especially the CHARSAVE.SC file, but learning the original names of some constants and procedures is extremely nice. I'm updating my sources to use those names now (except for Btst and Bclr... I prefer my names of getEventFlag and clearEventFlag).
Just to give a bit of status update, I had originally decompiled based on the v3.0.7 (I think?) SCICompanion, and had made a tonne of good progress renaming files, procedures and variables, but somewhere long the way, I made a mistake and nothing would compile. I tried backtracking, but had no joy. And then Phil updated the decompiled, so I figured I might as well just start over with decompiling based on the sco and game.ini files I'd already created. That's where I am now. Everything decompiled from scratch with my current variable names. My next step is to replace the constants for things like character class, or walk/run/sneak mode, or TimeOfDay (I.e. Dawn, Midday, sunset, Midnight, etc.) and so on and so forth. Now I'm compiling after each set of constants is replaced, and pushing to my local git server. I've tried allowing outside access, but there's a block somewhere along the chain (router, server, git, ??), and I haven't had a moment to sit down and troubleshoot.
A couple of things I've learned: I'm very impressed with the code for racking up puzzle points. There's a clever array that's significantly long, and they've made wrappers for it so you can toggle each bit in the array individually. At least a couple hundred... Very efficient, since each true/false flag only takes a single bit. So there's a bunch in there for game state stuff (like have you climbed up Henry's cliff side, or destroyed the nest outside the healer. Just everything like that. They also use these flags for puzzle points. So there's a specific procedure you call with the flag, the amount of points, and what character class it applies to (if it's only a specific class). I see a very easy potential to create a kid that lists exactly which points you missed at the end of the game, ala QFG5. Also I learned that if you're a Thief, and you get robbed in the alley, you lose 10 points. So that's fun.
Something else I found... Casting flame dart improves your throwing skill. I believe this is a bug, due to copy/pasta. The throwing file and the flame dart file are both very similar. I want to double check that one though, I didn't spend a lot of time reviewing it the 1st time through.
-
Thanks for the update - very interesting!
Btst and Bclr are used in many SCI games, and it's not limited to event flags, so you if you don't like the original names, you might want to use bitTest and bitClear etc. Names like getEventFlag is too specific.
EDIT: So I noticed that these functions are actually tied to a specific global flags array. Here's the version from QfG2:
(procedure (Btst flagEnum)
(& [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
;; (DisposeScript FLAGS)
)
(procedure (Bset flagEnum &tmp oldState)
(= oldState (Btst flagEnum))
(|= [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
oldState
;; (DisposeScript FLAGS)
)
(procedure (Bclr flagEnum &tmp oldState)
(= oldState (Btst flagEnum))
(&= [gameFlags (/ flagEnum 16)] (~ (>> $8000 (mod flagEnum 16))))
oldState
;; (DisposeScript FLAGS)
)
The puzzle points management is nice and similar to other QfG games. It's a very good idea to add a patch for displaying the missing points! I say go for it. :)
About the flame dart issue: you might find the discussion in the following ScummVM bug interesting:
https://bugs.scummvm.org/ticket/7133 (https://bugs.scummvm.org/ticket/7133)
It's about the problematic spell skill learning in QfG3 and even includes correspondence with Corey Cole.
-
Btest, Bset and Bclear are part of the SCI1.1 template game too:
http://scicompanion.com/Documentation/sci11_procedures.html
http://scicompanion.com/Documentation/Tutorial/Score.html#flags
-
All excellent points on keeping it as Btst, Bset and Bclr. Works for me.
I hadn't read anything on the template games documentation yet either, but I see those would be a good resource to use to learn some common conventions, too.
Edited to add, that's an interesting discussion about QFG3. I'd love to tackle some custom patches for that game, some time down the road. Classic problem: too much to do, to little time to do it.
-
I hadn't read anything on the template games documentation yet either, but I see those would be a good resource to use to learn some common conventions, too.
Yes, you should.
Or even better, go over Sierra's system scripts (the original "template" game): https://github.com/OmerMor/SCI16/tree/master/SYSTEM (https://github.com/OmerMor/SCI16/tree/master/SYSTEM).
This is their version for SCI16 from 1993 (1.001.099), but it should be pretty similar to the system scripts for QfG1EGA.
I also have earlier versions from SCI0. I'll upload them sometime as well.
-
Okay, so after a bunch of restarts then dropping it for almost 2 years, I picked this project back up.
I'm using the latest SCICompanion (built myself from the github source), with Hero's Quest v1.000. My thought being that when this is fully commented, then I can then re-implement the differences between released versions, ending with the QFG1EGA available on gog and steam.
I've made some decisions that may ruffle some feathers. Whenever possible, I'm ditching the defines, variable naming, and file naming conventions originally created by Brian for SCIStudio in favour of those originally used by Sierra. The SCI16 source code and documentation provided by OmerMor was a huge help in this regard. Even though it was for a later version of SCI than's used in HQ1, I went with the same assumption OmerMor alluded to earlier... that (for the most part) Sierra wouldn't have completely ditched working code without a good reason... they were a business afterall, and time costs money. So, I believe I've been able to accurately deduce most of the original global variable names (that weren't game specific).
As of now, I have completely commented/renamed the system scripts, as well as Main.sc. I have replaced all Control references (i.e. onControl and illegalBits) with names, instead of magic numbers. I've renamed all classes to their original names (i.e. Act is Actor, and Rm is Room, etc.). I've named almost all the HQ-specific global variables, and procedures.
The source code compiles without error, and the game runs. I haven't done a complete play-through, but I have wandered around and tried a few things. I know there is at least one game error related to either the decompile or the recompile (can't tell which), and possibly more [EDIT: the error is outside Baba Yaga's hut. I cannot enter the hut.]. I've already corrected one error (which I can share in a later post). I haven't gone bug hunting yet (other than one game crashing bug in the Waterfall room). I've been focused on variable/procedure/class renaming, while keeping the game compilable and at least runable.
This recent stretch has probably been a couple months of finding 10 minutes or an hour here, or an evening there. I'm a bit burned out on it for the moment, which is a shame, because I've only just gotten to the game proper now. Anyway, I wanted to share my progress.
-
Consider me impressed.
-
Scratch that error with Baba Yaga's hut... that exists in the original game. There are no known errors in the compilation. But emphasis on known, because I haven't done a full playthrough.
The error I did fix, btw, was in script 82 (Waterfalls.sc).
Here's the code as decompiled (which crashed as soon as it ran, during the room's init):
(procedure (localproc_054e &tmp temp0)
(= temp0 0)
(while (< temp0 8)
([local6 temp0]
setLoop: 0
ignoreActors:
x: [local38 temp0]
y: [local46 (= [local14 temp0] (Clone aFallScript))]
init:
setPri: 1
cycleSpeed: 3
setScript: [local14 (= [local6 temp0] (Clone waterFalling))] 0 temp0
)
(++ temp0)
)
)
And here's the correction I made that worked:
(procedure (SetWaterfallHighDetail &tmp i)
;CI: NOTE: SCICompanion incorrectly decompiled this script, as follows:
;
;y: [yHighDetail (= [waterfallScripts i] (Clone aFallScript))]
;setScript: [waterfallScripts (= [waterfallProps i] (Clone waterFalling))] 0 i
;
;instead of calling the Cloning and assignment first. This caused this room to
;crash immedaitely upon the room's init.
(= i 0)
(while (< i 8)
(= [waterfallProps i] (Clone waterFalling))
(= [waterfallScripts i] (Clone aFallScript))
([waterfallProps i]
setLoop: 0
ignoreActors:
x: [xHighDetail i]
y: [yHighDetail i]
init:
setPri: 1
cycleSpeed: 3
setScript: [waterfallScripts i] NULL i
)
(++ i)
)
)
Please ignore the change in variable/procedure names. I'm not sure exactly what the decompiler is trying to do there.
-
Please ignore the change in variable/procedure names. I'm not sure exactly what the decompiler is trying to do there.
Its best? I mean, those names aren't stored in the final script code, unlike selectors and class names and such.
-
Oops, I didn’t mean to phrase that as a single thought. I know the variable names aren’t stored anywhere in the compiled code, and the decompiler does an amazing job substituting reasonable variable names whenever possible.
What I meant was, I’m not sure what the decompiler is trying to do in the first code snippet. It looks like it’s trying to define a variable in one of the properties of that variable itself?
-
Very nice Charles!
I'll need to clear up some time and go over the code.
Have you considered putting it over github? That way you would be able to get comments and pull requests. I know you don't own the code, but I don't think anyone would bother - especially if you don't include any resource other than scripts (e.g. PICs and VIEWs).
-
Has anyone started any kind of repository of working decompiled game scripts?
-
Do you mean "working" as in if you do a Recompile All the result is playable?
I'd love to set something like that up, but I can never seem to get Git to let me push stuff, and I think Bitbucket has a limit on the amount of repos I can have so... best I could do is a read-only thing on my own server.
-
Yes, that is what I meant. Bitbucket is limited to five users for free, but allows unlimited private repos. Of course a repository could be public, even if it only had 5 contributors. GitHub is free for public and open source projects. I could do something with one of the Wikis, too, but it may not be as convenient for someone to update bug fixes or add comments.
-
Great work on this decompilation effort! I've been working to adapt this code for the QFG1EGA release (version 1.200). After downloading the code, I used the SCO files to help me in decompiling the newer version. Between the first and last releases, a lot has changed, not just the title.
As noted here (https://tcrf.net/Quest_for_Glory:_So_You_Want_to_Be_a_Hero_(1989)) at TCRF, the High Speed Hero option was removed. The Kobold's cave scripts were also massively overhauled. The Tavern added an additional note for earlier in the game. Finally, a lot of the game's text was revised.
-
OK, I have added a page on the Wiki for decompiled scripts. So far I only have one, King's Quest IV version 1.006.004, int. 0.000.502. This is just to start working on a repository page design. One advantage to the Wiki over a static page is that there can be multiple contributors. Users with Wiki accounts can upload updated/modified versions and add notes or more info to an entry. Any vandalism can be easily rolled back. There is also a discussion sub-page.
http://sciwiki.sierrahelp.com//index.php?title=Source_Repository
Not as ideal as a git, but it can be private or at least some what obscured. I can look into restricting access to only members. Not sure how important this would be to do these days.
-
It's a good source dump. I just felt it could use more gRoom where it has global2 now. Also went ahead and named almost everything in Class_255 but the script file itself. How do I get this to you?
Oh and there was something really weird about the copy protection script. I couldn't help myself, sorry. I think maybe it had a particularly messy skip patch? If you want to skip the CP, you really ought to just change KQ4::init to go to room 700 instead of 701 :D
-
You can attach it here. Also, if you want to contribute to repository I can add a Wiki account for you.
As to the CP script I forgot to remove the patch from the game's folder when I decompiled, so the decompiler used it instead of the original. It is messy because it was created with a hex editor many years ago. I am a bit torn about it. Since the archivist in me wants preserve the original, but CP annoys the f**k out of me. I guess the way to handle it is to decompile the original and just change the KQ4::init. That way the CP could easily be switched on or off.
-
just change the KQ4::init. That way the CP could easily be switched on or off.
That's what I was thinking, yeah.
Edit: missed a bit in rewriting the print statement.
-
That is throwing a bunch errors. I had it down to no errors. And now there is a TheMenuBar and a MenuBar? Looks like you changed proc255_ to Print and Class_255_0 to MenuBar. Is there possibly some confusion between proc255 and Class_255?
-
Possibly, possibly. I did neglect to check if it compiled, doing this all from my favorite text editor instead of SCI Companion...
Okay, I fixed it!
-
Just typing "Hello" results in errors and then gives the expected result:
-
Here is a decompile that does not include the CP skip patch. It is also using the name MenuBar instead of TheMenuBar. It does not include the changes you did, but there is no difference the print, so the problem must be elsewhere.
-
Just typing "Hello" results in errors and then gives the expected result:
Well, the message pretty much says what is happening, doesn't it? There's a said spec somewhere that's been corrupted. What the cause is, I can't say just from looking at it, but since these are only individual Said calls failing, the rest will surely work.(Or fail, and eventually pass execution to the pragmaFail routine, I believe, where an error is printed "You've left me speechless" or whatever the game chooses)
EDIT: Aaaaand, the first error is this, in TheMenuBar.sc:
(SetMenu 513 109 ',[/game]')
the number 500 (decimal; SCI Companion uses hex numbers, so it's 1f4 in the vocab view) resolves to 'game'.
The second error is this, in Main.sc (in one of the assembler parts):
lofsa 'chew/['
again, the number says so. Four things:
- Why do these broken said specs occur in the first place?
- Apparently, you can't sort by column in the vocab view (or maybe that's Wine's fault).
- Is it better to have the vocab view use hex or decimal?
- Both the corrupted words, save and bone, have a value of 240<=x<=255. Because of point 2, I can't tell if these are the only two problematic words.
-
So the problem really is that Companion carries a said spec around as a vector of uint16. This fails if a group number happens to fall within the range 240..255. Otherwise it works well. It would have to be a vector of tuples <uint16, bool> with the bool set to true if this is actually a word, false if it is an operator.
-
Judging from SV's disassembly, that should be "quit [/ game]" for the first one. The second, I can see "chew" "chew / earthworm" "chew / bone" "chew / fruit" and "chew / chicken" in the saids block.
-
No, quit game is already there:
(SetMenu 513 109 ',[/game]')
(SetMenu 514 109 'restore[/game]')
(SetMenu 516 109 'restart[/game]')
(SetMenu 517 109 'quit[/game]')
(SetMenu 769 109 'pause[/game]')
(SetMenu 770 109 'inventory')
The top one is bad; it has to be save game. Besides, quit has the group number 0x1d3 (467 decimal). It is not in the problematic range (save is 0xf0, which is in that range).
-
Good bug. I made a fix in my experimental branch of SCI Companion:
https://github.com/icefallgames/SCICompanion/commit/0f4582e5ae2d2c66b4876a73d7f1bb65126c4416
It doesn't look like github has a way to cherry pick a change from another branch, and I don't have the time to clone the main branch and do it myself (I just work on the experimental branch I use for cascadia quest). But if anyone (kawa?) feels inclined to make the same change on the main branch I'll approve it (of course it's possible they've diverged so much it's not straightforward, I dunno).
Attached are the words that could run into problems in a KQ4 decompile. In additino to those discovered so far, "rosella" might be a pretty obvious problem ;-)
-
I'll have to disable some of my own hacks, but I'll get on it.
Edit: compiling...
Edit: Now, Github does not work for me, at least not in the "put things back on" sense, but I've been tracking the repo Phil linked well enough to be up-to-date, so here's the latest build (http://helmet.kafuka.org/sci/SCICompanion.exe) with those vocab changes in it.
-
Quick warning, I just realized as my laptop booted up for today that I'd forgotten to revert one particular hack: this build doesn't have SCI Studio syntax. I missed it because I hadn't marked the changes.
Copy in the stash replaced.
-
I just tried Kawa's new build and did a fresh decompile from scratch. The resulting recompile no longer throws errors on input of "hello" or any of the works troflip found. Some of those words were inventory items.
The only changes that I have made are to skip the CP script and to set the cursor on the opening screen to normal from the wait icon. I also tried to fix the only warning the compiler was giving in the switch in the rm92 script that had duplicate case values. I changed them to be sequential, but don't know if that breaks anything.
-
I also tried to fix the only warning the compiler was giving in the switch in the rm92 script that had duplicate case values. I changed them to be sequential, but don't know if that breaks anything.
I believe the correct fix would just be to remove or comment out the second duplicate case statement. It's unreachable code in the original.
-
That room is Lolotte's throne room. I was able to get through it with no trouble after compiling with the duplicate case commented out.
-
Reapplied some global names and the Print family, this time with testing.
-
I don't know what version this is, so I can't give it an intricate name like Collector's KQ4, but here's SQ3 I guess? Bled to death on the second screen just to test. Got the first few globals and the Print family in.
Edit: wow, I added the #title selectors and such in Print's argument parser and suddenly the pushy clerk's prints use them too!
-
There was no VERSION file? The interpreter version can be extracted for the string in the EXE. If you don't feel like bothering you can upload it and I can check.
-
There was. I just didn't bother. I'll put a version in the next upload.
Fun fact: I thought it a bug in the recompile but it seems the intro allows typing?
-
I have 2 or 3 different versions of the game. It would probably be best to keep version info with the source.
Fun fact: I thought it a bug in the recompile but it seems the intro allows typing?
Never tried it.
-
I got a version 0.000.685 interpreter running game 1.018.
Edit: I also have a German multilang 1.052 and an Amiga version with only the resources.
-
Script differences between SQ3 1.018 main.sc and German 1.052 main.sc:
- gamefile_sh is now unnamed Class_993_0
- gVersion is no longer preset to "Space Quest III".
- Version defaults to 0.001, not 0.000.001.
- Version is read via a File instance, not the kernel.
- SQ3 has parseLang and printLang properties preset to 49.
- (= temp0 (proc944_4)) and (if temp0 (proc944_3)) around Buckazoids::showSelf, is a language switching thing.
...and various vocabulary additions with matching saidspec changes, but still handsOn during the title sequence.
(incidentally, my SQ3 German runs on S.old.114)
-
I assume that S.old.114 has support for fonts with diacritics.
-
It's funny you should say that. The 0.685 terp from SQ3 doesn't, yet not 24 hours ago I produced this screenshot?
(https://66.media.tumblr.com/2f3dfa23ddc87d0ed5e61efd269a5b05/tumblr_pgbt2vvPKS1r1hfpzo1_400.png)
That's KQ4 1988, SIERRA.EXE 0.274 with the green buttons, not 1989 SCIV.EXE 0.502 with the white buttons.
This is my 1252 font, but that makes no difference in the matter of i]support[/i].
Actually, I think I get what I did wrong just now when I tested SQ3's 0.685 for extended font support...
Edit: nope, still won't show 'em. Weird, huh?
-
Well, one difference between SCI0 and SCI01 is the encoding of the vocab resource. The latter uses a standard NUL terminator, the former sets the high bit on the last character. Which means that you can't have 8-bit stuff in the vocab resource at least. It's possible they thought they weren't quite ethnocentric enough in their font decoding and changed it.
-
And yet that's the chronologically first SCI0 game correctly asking for lööps :D
-
I have added your SQ3 source to the Wiki. Didn't troflip successfully decompile SQ4 CD for the SCI1.1 template?
-
SQ5 actually. Older releases included leftover object files like Droole.sco, and had SQ5 Roger and UI views and all that.
I should know :)
-
That's it. Now I remember about the simulator at the beginning of the game came up. I wonder if he has the source intact before it was modified for the template or if we would need to decompile it again.
-
The S.old.xxx series is SCI01. The KQ1 remake used one; it was playable for quite a while under FreeSCI back when it had no support for the newer memory model. We can't know for sure, but it's possible that these games were developed initially under SCI0 and gradually upgraded, so that only a few rooms take advantage of the extra memory.
We saw the same thing with the QFG4, GK1 and PQ4 demos, which are SCI1.1 unlike the final games. With these it is less likely that the whole game was developed under SCI1.1 initially. While Sierra took some pains to make the high-level interfaces compatible, the graphics systems underneath are entirely different. It still could be true, though.
-
After weeks of combing the code and identifying many local variables, I now have the decompiled source code for QFG1EGA, version 1.200! Obviously, some things were changed between 1.200 and 1.000, so I overhauled the game.sh file to reflect this, as well as gave more readable names for the event flags and shorter, more concise names for the inventory items.
I made sure to put as much of the comments from the HQ1 code into the QFG1EGA code. The names of some of the variables and procedures were changed, either to be consistent with Sierra's original names (such as Bclr and Btst) or to be more accurate (HasLockPickTools has been changed to CanPickLocks, for example)
The code compiles without any errors (save for warnings about duplicate case values, which are in the original scripts anyway). I have not yet played through the game to see if anything went awry.
11/3/2018 EDIT: Apparently, something DID go awry - the menu bar stopped working (because I removed the "The" from the menu bar class name by mistake) and the stats on the character sheet are over black boxes (the HQ1 code had that mistake by using the wrong define) Here's the new code with these mistakes fixed. Sorry for the inconvenience.
-
either to be consistent with Sierra's original names (such as Bclr and Btst)
I'm curious. How exactly do you know that's what they're supposed to be called?
-
The original names are based on the code snippets that OmerMor provided previously. Also, in the Main.sc of the HQ1 source zip, it is stated that Bclr and Btst are their original names.
-
It's just, SCI16.zip doesn't have a main.sc, last I checked.
-
He means in the main.sc of the decompiled HQ1 v1.000 source I did. I'd commented that they were originally called Bclr and Btst. My comments were based on OmerMor's code snippets, so it's just a single source confirmation.
That's awesome work Eric, I'm looking forward to comparing the differences between HQ1 v1.000 and QFG1 v1.200.
When you do get to test the game, give a look at Henry's Outlook. I know I had decompiler issues with that room, when I did HQ1 v1.000.
-
So I ask again, what snippets?
-
Thanks for the update - very interesting!
Btst and Bclr are used in many SCI games, and it's not limited to event flags, so you if you don't like the original names, you might want to use bitTest and bitClear etc. Names like getEventFlag is too specific.
EDIT: So I noticed that these functions are actually tied to a specific global flags array. Here's the version from QfG2:
(procedure (Btst flagEnum)
(& [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
;; (DisposeScript FLAGS)
)
(procedure (Bset flagEnum &tmp oldState)
(= oldState (Btst flagEnum))
(|= [gameFlags (/ flagEnum 16)] (>> $8000 (mod flagEnum 16)))
oldState
;; (DisposeScript FLAGS)
)
(procedure (Bclr flagEnum &tmp oldState)
(= oldState (Btst flagEnum))
(&= [gameFlags (/ flagEnum 16)] (~ (>> $8000 (mod flagEnum 16))))
oldState
;; (DisposeScript FLAGS)
)
From OmerMor on the 1st page of this very thread.
-
Ah. Thank you.
-
Eric,
I was just looking through your code myself, and I noticed that you left script n982 and n896 out of the game.ini. Even if you don't have a custom name for them, you should include them in the game.ini otherwise a Compile All won't include them.
Also, I believe n982 is SIGHT.SC. proc982_0 is IsOffScreen, proc982_1 is CantBeSeen, and proc982_2 is AngleDiff.
-
I based the game.ini file on the one for 1.000. While I accounted for the changes in the game scripts, I didn't account for additional system scripts.
Two system scripts added by the time 1.200 was released were Avoider and Sight.
Here is an updated version of the code, with a few more local variables identified as well as the additional system scripts.
As for test results...
The game seems to play okay, but certain rooms have gotten bugged. In the fairy ring, the mushrooms are not placed properly on the screen, and the game may crash with an "Oops!" error when the fairies approach you at night.
Entering the Meep's Peep is now completely impossible, since the game crashes with an "Oops!" error when trying to enter it. Good thing Razzle Dazzle Root Beer is there to bypass those bugged parts.
-
Here we go again! An updated decompile of QFG1EGA, using more original define and procedure names. Also, the SYSTEM, GAME, and KERNEL headers have been converted to Sierra Script, allowing for enum support. I've added in a DEBUG.BAT that automatically starts the game with the -d parameter making it easier to debug.
Now to just playtest this and work on the QFG1VGA decompile...