Community
SCI Programming => SCI Community How To's & Tutorials => Topic started by: lwc on January 11, 2024, 07:57:40 PM
-
Surprisingly, SCI0 games' internal debugger doesn't offer direct support for the most basic plot features in SCI - teleport and setting event flags.
But at least for teleport the wiki suggests an indirect way (http://sciwiki.sierrahelp.com/index.php/SCI_Debug_Modes#Internal_Debugger) - to modify global variable 13 into the desired room number.
Is there a similar indirect way to set/test/clear event flags (the debugger allows changing global, local and temp flags, but not event ones which are a subset of global variables)?
-
Some SCI0 games predate Sierra's use of flags, like KQIV and SQ3 which have none.
PQ2 is the first SCI0 game I know uses them, but if you look at the code for PQ2 and LSL3 the flags begin at different globals. PQ2 starts at global250 and LSL3 at global111.
To use the internal debugger you'd need to first know which global the flags start at for each specific game, figure out which subsequent global the flag is stored in, know the existing value of that global, and finally calculate the correct new value to set the proper flag while preserving the other 15 flags.
IMO, that's way too much work when you can use the script debugger which does all this for you.
Edit: LOL, your question made me realize I made a dumb mistake misusing flags in a project last year that I'll need to fix. Never take anything at face value I guess.
-
IMO, that's way too much work when you can use the script debugger which does all this for you.
Well, all the SCI0 games I know, like QFG1EGA, QFG2 (not the non SCI fan remake) and LSL3 don't have anything like flag control in their script debuggers, despite using flags for everything. I would have mentioned SQ3 too, but judging from its code you seem to be right that it predates flags.
Edit: LOL, your question made me realize I made a dumb mistake misusing flags in a project last year that I'll need to fix. Never take anything at face value I guess.
At your service...
-
I guess you're right, they don't have flag commands in the script debuggers. That is weird. WTH?
-
It's easy to add this feature, and I guess I already did in my Debug repo (https://github.com/Doomlazer/SCI-Debug-Resources/blob/main/PQ2/ExtraCommands/PQ2%20README.TXT) for PQ2:
script.801:
debugRm::handleEvent
...
(KEY_ALT_f ;add flag set and clear
(= i (proc255_3 {Flag Number:}))
(if (proc0_18 i)
(proc255_0 {clearing flag})
(proc0_17 i)
else
(proc255_0 {setting flag})
(proc0_16 i)
)
)
...
I should go through all the SCI0 games and add Test/Set/Clear for them eventually.
-
I should go through all the SCI0 games and add Test/Set/Clear for them eventually.
But each will need a different shortcut (e.g. in LSL3 alt+f is already taken).
To use the internal debugger you'd need to first know which global the flags start at for each specific game, figure out which subsequent global the flag is stored in, know the existing value of that global, and finally calculate the correct new value to set the proper flag while preserving the other 15 flags.
What do you mean start at VS stored at? All SCI games that have event flags have just a single global for event flags (disregarding alternate dedicated event flags like eco2's Ecorder (https://sciprogramming.com/community/index.php?topic=9185.msg74915#msg74915)).
Anyway, in the example of lsl3 if you check global 111 after running various setflag it seems to stay at 0 if the setflag is > 15 (e.g. look at plaque or use binocular at room 200), so how can you tell any flag from a value of 0?
-
Each global can only store 16 flags. According to this list of flags (https://github.com/EricOakford/SCI-Decompilation-Archive/blob/8b0b5c0edd2a8c57bafca3ae5446a27c597633d6/lsl3/src/flags.sh#L23C9-L23C9) fLookedAtPlaque is flag 18.
If global111 stores flags 0-15, global112 would contain flag 18. When no other flag is set in global112, looking at the plaque should change the value from zero to four since it is setting the third bit.
According to the SDA, LSL3 Main.sc (https://github.com/EricOakford/SCI-Decompilation-Archive/blob/8b0b5c0edd2a8c57bafca3ae5446a27c597633d6/lsl3/src/Main.sc#L145) reserves global111-global117 for flags. That's a total of 112 possible flags, even though less than 80 flags are actually used by the game. Using flag 112 or above in LSL3 would start altering globals reserved for other uses and likely introduce bugs.
Of course I've been wrong before, so you should verify all of this before accepting it as fact.
-
I can confirm everything doomlazer just said. There are 80 defined flags in the OG source and beenIn203 as it's called there is #18.
The 117th global is named lastFlag and is not, to my knowledge, used. The next likely things to affect by changing flags higher than 78 are the player's name, the nature of the closest person, the speed test result, and the filth level. None of those are too bad when corrupted but then we get to the event handlers at global 125-127.
-
Thanks! I wonder why sluicebox's newer repository has no SH files, if they're that useful.
But be it as it may, in ScummVM they always look in just 1 single global, in this case they look just at flag 111 (https://github.com/scummvm/scummvm/blob/master/engines/sci/engine/features.cpp#L924), so how would they know to continue looking until 117?
-
But be it as it may, in ScummVM they always look in just 1 single global, in this case they look just at flag 111 (https://github.com/scummvm/scummvm/blob/master/engines/sci/engine/features.cpp#L924), so how would they know to continue looking until 117?
That function simply returns the first global used for flags, as doomlazer said. Look instead at the code in console.cpp that does the actual flag manipulation. It only does very basic range checking (are you trying to read/write past the last global variable?), but does check that the global is a number, because no flag variable should ever be anything else:
// read the global that contains the flag
uint16 globalNumber = _gameFlagsGlobal + (flagNumber / 16);
if (globalNumber > s->variablesMax[VAR_GLOBAL]) {
debugPrintf("Invalid flag: %d (global var %d is out of range)\n", flagNumber, globalNumber);
continue;
}
reg_t *globalReg = &s->variables[VAR_GLOBAL][globalNumber];
if (!globalReg->isNumber()) {
debugPrintf("Invalid flag: %d (global var %d is not a number)\n", flagNumber, globalNumber);
continue;
}
uint16 globalValue = globalReg->toUint16();
uint16 flagMask;
if (g_sci->_features->isGameFlagBitOrderNormal()) {
flagMask = 0x0001 << (flagNumber % 16);
} else {
flagMask = 0x8000 >> (flagNumber % 16);
}
The last bit of code also answers your question about how to change the flag variables. Some games order their flags from MSB to LSB, others do the opposite.
-
Thanks! I wonder why sluicebox's newer repository has no SH files, if they're that useful.
The flag defines don't exist in the resources for a decompiler to recover. It's my understanding that EO sussed out the flags in that file I linked from examining the code by hand.
-
That function simply returns the first global used for flags, as doomlazer said. Look instead at the code in console.cpp that does the actual flag manipulation.
Not sure I understand, how does it know what is the last global used for flags? There's not even a loop there.
-
It doesn't. "It only does very basic range checking".
-
It knows only how many global variables there are, so if there's let's say 128 global variables and the flags start at global 111, you know for a fact any flag number divided by 16, plus 111, cannot result in a number higher than 128. So flag 12 is fine (12/16=0), flag 73 is fine (73/16=4, 4+111=115), flag 99 is fine (99/16=6, 6+111=117). Flag 420 is out of bounds (420/16=26, 26+111=137) because we know there's only 128 globals (this is defined by the heap part of script 0). But neither the game nor ScummVM can tell how far the flags go (though lastFlag could help here, it doesn't really need to for the programmer's sake because of the defined flag names), so it's perfectly allowed to play LSL3, set flag 128, and find your character's name is altered.
-
flag 73 is fine (73/16=4, 4+111=115), flag 99 is fine (99/16=6, 6+111=117).
So the general formula is:
initialGlobal+int(flag/16)
Does it mean in LSL3's room's initial room 200 when you look at the plaque, flag event 18 is set in global 112 due to 111+int(18/16) (111+1) and likewise when you use binocular for flag event 16 due to 111+int(16/16)?
-
Does it mean in LSL3's room's initial room 200 when you look at the plaque, flag event 18 is set in global 112 due to 111+int(18/16) (111+1) and likewise when you use binocular for flag event 16 due to 111+int(16/16)?
In the OG code, global 111 is named flagArray and its definition is followed by an enumeration of flags by name. Each first flag in a set of sixteen has its matching global number in a comment, so it goes:
logging ;*** 111
loadDebugNext
....
inQA
forceAtest
beenIn206 ;*** 112
beenIn200
beenIn203
...
beenIn266 ;*** 113
...
and so on.
beenIn206 is the binocular peep show and beenIn203 is the plaque. So your math does not check out. Both events are global 112: 16 (plaque) divided by 16 is 1, so [flagArray 1] aka global 112. Then 16 modulo 16 makes 0, so it's the first (zeroth) bit in that global. Likewise, 18 (peepshow) divided by 16 is also 1, but 18 mod 16 is 2, so it's the second-from-zeroth bit of that same global.
(Note that LSL3 uses most-to-least flag ordering so it's actually $8000 for the plaque and $2000 for the peepshow, not 1 and 4. But ScummVM is aware of this and the script debugger uses the same flag procedures as the rest of the game so this doesn't matter.)
(And of course "zeroth" in this case just means it takes $8000 and shifts it left/divides it by two zero times. Or takes 1 and shifts it right/multiplies it by two zero times for least-to-most flag ordering.)
-
So your math does not check out. Both events are global 112: 16 (plaque) divided by 16 is 1, so [flagArray 1] aka global 112. Then 16 modulo 16 makes 0, so it's the first (zeroth) bit in that global. Likewise, 18 (peepshow) divided by 16 is also 1, but 18 mod 16 is 2, so it's the second-from-zeroth bit of that same global.
Sorry, but your line seems to contradict itself: "Both events are global 112" is exactly what my math indicated ("when you look at the plaque, flag event 18 is set in global 112...and likewise when you use binocular for flag event 16"), so why does it not check out?
Also note Plaque is 18, not 16 and binocular is 16, not 18.
-
I was more tired than the size of my post would indicate.
-
So if we have a working math formula to discover which global to use in the debugger. Now all that's left is a formula to know what to write into that global.
Some examples:
- The initial value of 112 is $4000.
- If your only action is looking at the plaque (flag 18), it turns 112 from $4000 to $6000.
- If your only action is using the binocular (flag 16), it turns 112 from $4000 to $c000.
- If you do both, it turns 112 to $e080.
Is there any formula to know what to write in the debugger to 112 in order to trigger those flags?
-
You take the value that's already there, calculate $8000 >> (flagNum % 16), and combine 'em with a binary (not logical) OR. That's what SetFlag does, and that's what you can do.
Going by your example, $4000 would be 0100'0000'0000'0000 in binary. Flag 18 % 16 is 2, so we take $8000 >> 2 = $2000 is 0010'0000'0000'0000. (If LSL3 were least-first ordered you'd do $0001 << 2 instead to get 4)
0100'0000'0000'0000 => $4000, your initial state
0010'0000'0000'0000 => $2000, looking at the plaque
OR that together (the |= in SetFlag) and you get
0110'0000'0000'0000 => $6000
If we [t]then[/t] look at the binoculars, it takes $8000 >> (16 % 16) = still $8000, and we get
0110'0000'0000'0000 => $6000, previous state
1000'0000'0000'0000 => $8000, looked through binoculars
makes
1110'0000'0000'0000 => $E000
-
And since four bits equals one hex digit, you can make a simple table and use that. You're only going to need four columns, assuming you only set/clear one bit at a time.
-
And since four bits equals one hex digit, you can make a simple table and use that. You're only going to need four columns, assuming you only set/clear one bit at a time.
Any way to replace this table with a simple formula (like the one to find the relevant global)? ;)
-
As explained several times, $8000 >> (x % 16). It is equivalent to 215/2x. Then OR with that to set the bit, or AND with the complement to clear it.
-
@lwc, I've updated (http://sciwiki.sierrahelp.com/index.php?title=SCI_Debug_Modes&type=revision&diff=16144&oldid=16143) the LSL3 debug info (http://sciwiki.sierrahelp.com/index.php/SCI_Debug_Modes#Leisure_Suit_Larry_3) on the SCIWiki and created a patch file (https://github.com/Doomlazer/SCI-Debug-Resources/tree/main/LSL3/ExtendedCommands) in my personal repo that adds flag testing and toggling.
-
Nice! You might want to fix that Alt+I does work in the regular debug (allows typing during cutscenes), and maybe link to your patch there.
-
You can upload the patch to the Wiki. You should have upload rights. Just look at the source for the EQ1CD debug package to see how to create the link. After you save it just click on the red link it creates to open the upload dialog.
-
You can upload the patch to the Wiki. You should have upload rights. Just look at the source for the EQ1CD debug package to see how to create the link. After you save it just click on the red link it creates to open the upload dialog.
Do you mean me? I don't even have edit rights, since the wiki is closed for registering.
The only top right link is "log in" for existing users (try InCognito if you're already logged in).
And if you try to hack it by going to the secret register link, it tells you:
You do not have permission to do that, for the following reason:
You are not allowed to execute the action you have requested.
Actually, the homepage there confirms it's on purpose and instructs to contact you, so may you please register me? :)