Finally understanding IFs, WHILEs, ANDs, ORs and ELSEs

Anything to do with GTA1/GTA2 modding (tools, scripts and more).
Post Reply
User avatar
valps
Car Jacker
Car Jacker
Posts: 32
Joined: 31 Dec 2020, 15:03
GH nick: Valps
Location: Brazil
Contact:

Finally understanding IFs, WHILEs, ANDs, ORs and ELSEs

Post by valps »

With the help of the T.M. documentation in this link (https://gtamp.com/GTA2/SCR%20Strings-ar ... ormat.html) and the source code of his decompiler, I guess I figured how IF's, WHILE's, AND's, OR's and ELSEs works in compiled script files ".scr"

Apparentely there is a preferred and ""correct"" way for nesting ANDs and ORs.

The "correct" form: ( ( (...) AND (...) ) AND (...) ) AND (...) [I will call it the 'lefty' form]

The "wrong" and potentially bugged nestings:

(...) AND ( (...) AND ( (...) AND (...) ) )
(...) AND ( ( (...) AND (...) ) AND (...) )
( (...) AND (...) ) AND ( (...) AND (...) )
etc.

Mixing ANDs and ORs:

100% wrong: ( #1 ) OR ( ( #2 ) AND ( #3 ) )
potentially working: ( ( #2 ) AND ( #3 ) ) OR ( #1 )

You may ask why? Spoiling some conclusions:

- I compiled some codes with "wrong" nestings and I noted that the order of brackets of ANDs and ORs are not respected. Only compiling the codes with the "lefty" form I found a reasonable pattern for the IF_JUMP command.
- Billy Thompson, in his missions scripts for Industrial District he always used the 'lefty' form whenever nesting ANDs. There can be another reasons for that (it's easer to count the brackets) but I suspect that he knew that the compiler is buggy when nesting in other ways.

In order to properly answer this question, I studied the hex data of IFs, WHILE's, AND's, OR's, ELSE's and NOT's compiling many simple codes and looking at the compiled files using a HEX editor.

First, we need to know what is the IF_JUMP command in T.M. decompiler.

The possible structure of IF_JUMP in hex data:

1° form: (cpi) 62 00 (npi) (AND) (OR) (ENDIF index)
2° form: (cpi) 62 00 (aci) (AND: 00 00) (OR: 00 00) or else goto (eci)

where

cpi: current point index
npi: next point index -> always enter that point index after finishing the command the 'npi' is in
aci: access command index if receives TRUE
eci: exit command index if receives FALSE

The first form occurs when if at least (AND) or (OR) are non-zero.
If (AND) and (OR) are both 00 00, the second form of IF_JUMP always occurs and the last index in this case represents the exit index

Example of a IF command code (Don't criticize its senseless, it was created just to be decompiled):

Code: Select all

DO_NOWT
IF ( ( ( ( true17 = 255 ) AND ( true16 = 255 ) ) AND ( true15 = 255 ) ) AND ( true14 = 255 ) )
   CHANGE_POLICE_LEVEL (4)
   CHANGE_POLICE_LEVEL (6)
ENDIF
CHANGE_POLICE_LEVEL (3)
LEVELEND
In hex data:

15 00 01 01 16 00 00 00 (cpi: 15 00) DO_NOWT (npi: 16 00)

16 00 5E 00 18 00 00 00 12 00 FF 00 (cpi: 16 00) S_EQUAL_I (npi: 18 00) (WHILE_EXEC: No) (counter index: 12 00) (check value in Hex: FF 00)
17 00 5E 00 1A 00 00 00 11 00 FF 00 (cpi: 17 00) S_EQUAL_I (npi: 1A 00) (WHILE_EXEC: No) (counter index: 11 00) (check value in Hex: FF 00)

18 00 62 00 17 00 01 00 00 00 20 00 (cpi: 18 00) IF_JUMP (npi: 17 00) (AND: Yes) (OR: No) (ENDIF index 20 00)

19 00 5E 00 1C 00 00 00 10 00 FF 00 (cpi: 19 00) S_EQUAL_I (npi: 1C 00) (WHILE_EXEC: No) (counter index: 10 00) (check value in Hex: FF 00)

1A 00 62 00 19 00 01 00 00 00 20 00 (cpi: 1A 00) IF_JUMP (npi: 19 00) (AND: Yes) (OR: No) (ENDIF index 20 00)

1B 00 5E 00 20 00 00 00 0F 00 FF 00 (cpi: 1B 00) S_EQUAL_I (npi: 20 00) (WHILE_EXEC: No) (counter index: 0F 00) (check value in Hex: FF 00)

1C 00 62 00 1B 00 01 00 00 00 20 00 (cpi: 1C 00) IF_JUMP (fci: 1B 00) (AND: Yes) (OR: No) (ENDIF index 20 00)

1D 00 A2 01 1E 00 00 00 00 00 04 00 (cpi: 1D 00) CHANGE_POLICE_LEVEL (npi: 1E 00) (WHILE_EXEC: No) (??) (argument: 4)
1E 00 A2 01 21 00 00 00 40 CA 06 00 (cpi: 1E 00) CHANGE_POLICE_LEVEL (npi: 21 00) (WHILE_EXEC: No) (??) (argument: 6)

20 00 62 00 1D 00 00 00 00 00 21 00 (cpi: 20 00) IF_JUMP (aci: 1D 00) (AND: No) (OR: No) or else (eci: 21 00)

21 00 A2 01 22 00 00 00 B0 C6 03 00 (cpi: 21 00) CHANGE_POLICE_LEVEL (npi: 22 00) (WHILE_EXEC: No) (??) (argument: 3)
22 00 3C 00 FF FF (cpi: 22 00) LEVELEND (Finish)


Note: CHANGE_POLICE_LEVEL = A2 01. I'm not sure about the WHILE_EXEC, but it will be 01 00 if that code is in a WHILE_EXEC.


- How you can understand that IF block using hex data?

(I will put brackets between two 2byte hex so it is easer to read)

In the case of the atributtion logic operator ( counter = [int] ), it justs starts with it:

[16 00] 5E 00 [18 00] 00 00 [12 00] FF 00 (cpi: 16 00) S_EQUAL_I (npi: 18 00) (WHILE_EXEC: No) (counter index: 12 00) (check value in Hex: FF 00)

It does not indicate that there is an IF or WHILE, it justs appear there.

IMPORTANT: In the first nesting of ANDs, the 'npi' apparently ALWAYS point two index after the current index (16 00 -> 18 00) and this index SHOULD be an IF_JUMP, which is

[18 00] 62 00 [17 00] 01 00 [00 00] 20 00 (cpi: 18 00) IF_JUMP (npi: 17 00) (AND: Yes) (OR: No) (ENDIF index 20 00)

Now the 'npi' link the previous logic operator with the next operator, which is on index 17 00:

[17 00] 5E 00 [1A 00] 00 00 [11 00] FF 00 (cpi: 17 00) S_EQUAL_I (npi: 1A 00) (WHILE_EXEC: No) (counter index: 11 00) (check value in Hex: FF 00)

Now I think the gta2.exe will calculate the boolean value of ( true17 = 255 ) AND ( true16 = 255 ) and store it.

The new 'npi' now can point to the ENDIF index or to another IF_JUMP, which is the case here because there is another boolean check to do:

[1A 00] 62 00 [19 00] 01 00 [00 00] 20 00 (cpi: 1A 00) IF_JUMP (npi: 19 00) (AND: Yes) (OR: No) (ENDIF index 20 00)

The 'npi' must point to a boolean checking ( true15 = 255 ), which in this case is

[19 00] 5E 00 [1C 00] 00 00 [10 00] FF 00 (cpi: 19 00) S_EQUAL_I (npi: 1C 00) (WHILE_EXEC: No) (counter index: 10 00) (check value in Hex: FF 00)

The next sequence of commands is (in the order these are executed)
[1C 00] 62 00 [1B 00] 01 00 [00 00] 20 00 (cpi: 1C 00) IF_JUMP (npi: 1B 00) (AND: Yes) (OR: No) (ENDIF index 20 00)
[1B 00] 5E 00 [20 00] 00 00 [0F 00] FF 00 (cpi: 1B 00) S_EQUAL_I (npi: 20 00) (WHILE_EXEC: No) (counter index: 0F 00) (check value in Hex: FF 00)

Now the 'npi' points to a IF_JUMP in which AND = 0 and OR = 0, so there is an ENDIF:

[20 00] 62 00 [1D 00] 00 00 [00 00] 21 00 (cpi: 20 00) IF_JUMP (aci: 1D 00) (AND: No) (OR: No) or else (eci: 21 00)

This means that the nesting of ANDs ended, and the gta2.exe will decide if it enter the IF block or ignore it.

If the returning boolean is TRUE, npi = aci = 1D 00.
If the returning boolean is FALSE, npi = eci = 21 00.

The index 1D 00 is the first command in this IF block, the index 21 00 is the first command outside this IF block.


- But if there is an ELSE in the IF block, how can I know?

The easy way to check this is looking at the 'eci' index in the IF_JUMP ENDIF. If 'eci' is higher than 'cpi', then there is no ELSE in the IF block.


- Example of a IF command with ELSE:

Code: Select all

IF ( true17 = 255 )
   CHANGE_POLICE_LEVEL (4)
ELSE
   CHANGE_POLICE_LEVEL (6)
ENDIF
CHANGE_POLICE_LEVEL (3)
LEVELEND

In hex data:

15 00 5E 00 19 00 00 00 12 00 FF 00 (cpi: 15 00) S_EQUAL_I (npi: 19 00) (EXEC: No) (counter index) (counter value in Hex)

16 00 A2 01 1A 00 00 00 C0 DD 04 00 (cpi: 17 00) CHANGE_POLICE_LEVEL (npi: 1A 00) (WHILE_EXEC: No) (??) (argument: 4)
17 00 A2 01 1A 00 00 00 04 D4 06 00 (cpi: 18 00) CHANGE_POLICE_LEVEL (npi: 1A 00) (WHILE_EXEC: No) (??) (argument: 6)

19 00 62 00 16 00 00 00 00 00 17 00 (cpi: 19 00) IF_JUMP (aci: 16 00) (AND: No) (OR: No) or else (eci: 17 00)

1A 00 A2 01 1B 00 00 00 FE FF 03 00 (cpi: 1A 00) CHANGE_POLICE_LEVEL (npi: 1B 00) (WHILE_EXEC: No) (??) (argument: 3)
1B 00 3C 00 FF FF (cpi: 1B 00) LEVELEND (Finish)

As you can see, 'eci' < 'cpi' in the ENDIF IF_JUMP, and it locate exactly where the ELSE is: after the command 16 00 and before 17 00


- How can I differ a IF from a WHILE?

The difference is that if there is a WHILE, a GOTO command will appear at the end, right after the ENDIF. This makes sense, since WHILE is just a IF with a GOTO in the end.


- Example of using WHILE:

Code: Select all

WHILE ( true17 = 255 )
   CHANGE_POLICE_LEVEL (4)
   CHANGE_POLICE_LEVEL (6)
ENDWHILE
CHANGE_POLICE_LEVEL (3)
LEVELEND

In hex data:

15 00 5E 00 18 00 00 00 12 00 FF 00 (cpi: 15 00) S_EQUAL_I (npi: 18 00) (WHILE_EXEC: No) (counter index) (counter value in Hex)

16 00 A2 01 17 00 00 00 E8 5D 04 00 (cpi: 16 00) CHANGE_POLICE_LEVEL (npi: 17 00) (WHILE_EXEC: No) (??) (argument: 4)
17 00 A2 01 1B 00 00 00 00 00 06 00 (cpi: 17 00) CHANGE_POLICE_LEVEL (npi: 1B 00) (WHILE_EXEC: No) (??) (argument: 6)

18 00 62 00 16 00 00 00 00 00 1C 00 (cpi: 18 00) IF_JUMP (aci: 16 00) (AND: No) (OR: No) or else (eci: 1C 00)
1B 00 4D 00 15 00 00 00 18 00 00 00 (cpi: 1B 00) GOTO (npi: 15 00) (WHILE_EXEC: No) (ENDIF index: 18 00) (??)

1C 00 A2 01 1D 00 00 00 00 00 03 00 (cpi: 17 00) CHANGE_POLICE_LEVEL (npi: 1B 00) (WHILE_EXEC: No) (??) (argument: 3)
1D 00 3C 00 FF FF (cpi: 1D 00) LEVELEND (Finish)


Look at the code flow if S_EQUAL_I is TRUE:
15 00 -> 18 00 -> 16 00 -> 17 00 -> 1B 00 -> 15 00

Conclusion: If you want to know if there is a IF block or a WHILE block, you must check if there is a GOTO command right before the first command outside that block, which is

1C 00 A2 01 1D 00 00 00 00 00 03 00 (cpi: 17 00) CHANGE_POLICE_LEVEL (npi: 1B 00) (WHILE_EXEC: No) (??) (argument: 3)

I'm not sure but apparently you can also check if there is a GOTO command three index after the ENDIF (18 00 -> 1B 00), which is more easer to do.


- What if there is a NOT operator in the boolean checking?

The NOT operator (value 47 00) appears explicitely in hex data and it ALWAYS refer to the previous boolean check:

(cpi) 47 00 (npi) (??)


Example:

15 00 01 01 16 00 00 00 (cpi: 15 00) DO_NOWT (npi: 16 00)

16 00 5E 00 17 00 00 00 12 00 FF 00 (cpi: 16 00) S_EQUAL_I (npi: 17 00) (EXEC: No) (counter index: 12 00) (check value in Hex: FF 00)

17 00 47 00 19 00 00 00 (cpi: 17 00) NOT (npi: 19 00) (??)

18 00 5E 00 1B 00 00 00 11 00 FF 00 (cpi: 18 00) S_EQUAL_I (npi: 1B 00) (EXEC: No) (counter index: 11 00) (check value in Hex: FF 00)

19 00 62 00 18 00 01 00 00 00 1F 00 (cpi: 19 00) IF_JUMP (npi: 18 00) (AND: Yes) (OR: No) (ENDIF index 1F 00)

This is the same of

DO_NOWT
IF/WHILE ( ( NOT ( true17 = 255 ) ) AND ( true16 = 255 ) ...... )

where

'true17' index = 12 00
'true16' index = 11 00

Conclusion: the NOT operator always appears immediately after the boolean checking it is applying.


- Why is the nesting ( #1 ) OR ( ( #2 ) AND ( #3 ) ) 100% wrong?

After compiling this code

Code: Select all

DO_NOWT
IF ( ( true17 = 255 ) OR ( ( true16 = 255 ) AND ( true15 = 255 ) ) )
   CHANGE_POLICE_LEVEL (4)
   CHANGE_POLICE_LEVEL (6)
ENDIF
CHANGE_POLICE_LEVEL (3)
LEVELEND

I found:


15 00 01 01 16 00 00 00 (cpi: 15 00) DO_NOWT (npi: 16 00)

16 00 5E 00 18 00 00 00 12 00 FF 00 (cpi: 16 00) S_EQUAL_I (npi: 18 00) (EXEC: No) (counter index) (counter value in Hex)
17 00 5E 00 19 00 00 00 11 00 FF 00 (cpi: 17 00) S_EQUAL_I (npi: 19 00) (EXEC: No) (counter index) (counter value in Hex)
18 00 5E 00 1A 00 00 00 10 00 FF 00 (cpi: 18 00) S_EQUAL_I (npi: 1A 00) (EXEC: No) (counter index) (counter value in Hex)

19 00 62 00 1E 00 01 00 00 00 1E 00 (cpi: 19 00) IF_JUMP (npi: 1E 00) (AND: Yes) (OR: No) (ENDIF index 1E 00)
1A 00 62 00 17 00 00 00 01 00 1E 00 (cpi: 1A 00) IF_JUMP (npi: 17 00) (AND: No) (OR: Yes) (ENDIF index 1E 00)

1B 00 A2 01 1C 00 00 00 58 CA 04 00 (cpi: 1B 00) CHANGE_POLICE_LEVEL (npi: 1C 00) (EXEC: No) (??) (argument #1)
1C 00 A2 01 1F 00 00 00 28 CA 06 00 (cpi: 1C 00) CHANGE_POLICE_LEVEL (npi: 1F 00) (EXEC: No) (??) (argument #1)

1E 00 62 00 1B 00 00 00 00 00 1F 00 (cpi: 1A 00) IF_JUMP (aci: 1B 00) (AND: No) (OR: No) or else (eci: 1F 00)

1F 00 A2 01 20 00 00 00 C8 71 03 00 (cpi: 1F 00) CHANGE_POLICE_LEVEL (npi: 20 00) (EXEC: No) (??) (argument #1)
20 00 3C 00 FF FF (cpi: 20 00) LEVELEND (Finish)


Note that the compiled code is applying a OR between ( true17 = 255 ) and ( true15 = 255 ), and the first S_EQUAL_I is pointing at another S_EQUAL_I, which is weird and probably it is not the intended way.

You may ask, why the code is skipping the second "S_EQUAL_I".

Based on this question and other testings I have done, I concluded that the first IF_JUMP of the nested ANDs and ORs should always be at the index 'npi' of the previous boolean checking.

If we assume this, there won't be any S_EQUAL_I pointing at another S_EQUAL_I. Then S_EQUAL_I will always pointing at a IF_JUMP.

This explains why the "lefty" form of nesting ANDs and ORs is the ""correct"" way if considering the weird behavior of the miss2.exe compiler.

I hope all this information may help someone to finally finish the T.M.s decompiler.
Last edited by valps on 12 Oct 2024, 01:45, edited 1 time in total.
User avatar
Sektor
Boss
Boss
Posts: 1431
Joined: 04 Mar 2008, 06:51
GH nick: Sektor
Location: GTAMP.com
Contact:

Re: Finally understanding IFs, WHILEs, ANDs, ORs and ELSEs

Post by Sektor »

Great work!
User avatar
B-$hep
Immortal
Posts: 578
Joined: 24 Apr 2009, 21:43
GH nick: B-Shep
Location: EU

Re: Finally understanding IFs, WHILEs, ANDs, ORs and ELSEs

Post by B-$hep »

Did you know that original miss2.exe from DMA Design generated .txt file which had exactly al,ost everything you posted included.
It had all the jump indexes etc.

Not so detailed investigation but those txt files helped me with my new SCR compiler.
The patched miss2.exe wont generate temp files.

Good work Valps
Always wear safety glasses while programming.
User avatar
valps
Car Jacker
Car Jacker
Posts: 32
Joined: 31 Dec 2020, 15:03
GH nick: Valps
Location: Brazil
Contact:

Re: Finally understanding IFs, WHILEs, ANDs, ORs and ELSEs

Post by valps »

B-$hep wrote: 12 Oct 2024, 06:32 Did you know that original miss2.exe from DMA Design generated .txt file which had exactly al,ost everything you posted included.
It had all the jump indexes etc.
No, I didn't. However I'm using the original miss2.exe, so I always got the .txt and .tmp files, but I always have ignored them :lol:

I see that the .txt files don't give all informations about the IF_JUMP command, such as if it's using an AND or OR, or if it's an ENDIF. Now we know that

AND = 01 00 00 00
OR = 00 00 01 00

and that (as T.M. explained in his doc):

ENDIF = 00 00 00 00

I saw "01 00 01 00" in an original compiled mission script "wil_zh2.scr", I compared it with my open source downtown script and I found that apparently it is meant to be an OR I guess. It is a WHILE that checks if all yakuza assassins are died before finishing the mission "Bank Van Theft" (after parking the bank van in the garage).
User avatar
JernejL
Hitman
Hitman
Posts: 144
Joined: 21 Feb 2010, 22:03
GH nick: RedShirt

Re: Finally understanding IFs, WHILEs, ANDs, ORs and ELSEs

Post by JernejL »

B-$hep wrote: 12 Oct 2024, 06:32 Did you know that original miss2.exe from DMA Design generated .txt file which had exactly al,ost everything you posted included.
It had all the jump indexes etc.

Not so detailed investigation but those txt files helped me with my new SCR compiler.
The patched miss2.exe wont generate temp files.

Good work Valps

which patched miss2 is that? the mis pad hacked version or some other?

does my patched version still work on modern windows?
User avatar
B-$hep
Immortal
Posts: 578
Joined: 24 Apr 2009, 21:43
GH nick: B-Shep
Location: EU

Re: Finally understanding IFs, WHILEs, ANDs, ORs and ELSEs

Post by B-$hep »

JernejL wrote: 14 Oct 2024, 15:39 which patched miss2 is that? the mis pad hacked version or some other?

does my patched version still work on modern windows?
Mispad hacked yes.
works fine for me on every windows i use.
XP, 7, 10.

No issues on my side
Always wear safety glasses while programming.
User avatar
JernejL
Hitman
Hitman
Posts: 144
Joined: 21 Feb 2010, 22:03
GH nick: RedShirt

Re: Finally understanding IFs, WHILEs, ANDs, ORs and ELSEs

Post by JernejL »

B-$hep wrote: 19 Oct 2024, 10:05
JernejL wrote: 14 Oct 2024, 15:39 which patched miss2 is that? the mis pad hacked version or some other?

does my patched version still work on modern windows?
Mispad hacked yes.
works fine for me on every windows i use.
XP, 7, 10.

No issues on my side

funny and great that it still works.. it's a pretty primitive hack, all i had to do is send some menu messages and control which file it opens:

mispad writes registry key
hacked miss2 loads different shell32.dll (i think)
mispad runs it
sends to it a message to trigger file open menu
instead of open dialog, hacked dll reads registry key of file to compile and returns it back
mispad reads miss2 window contents back
mispad closes miss2

TL;DR: magic. Anyone can write an app to use hacked miss2 compiler in a better text editor nowdays.
User avatar
B-$hep
Immortal
Posts: 578
Joined: 24 Apr 2009, 21:43
GH nick: B-Shep
Location: EU

Re: Finally understanding IFs, WHILEs, ANDs, ORs and ELSEs

Post by B-$hep »

Yeh, I also wrote ScriptPad years ago. But didnt finish it.
I have it on my backups somewhere. I wanted to add lots of cool stuff. Like autosuggestion, autocompletion etc..
viewtopic.php?t=221&hilit=scriptpad

But I also made new script compiler, which doesnt need any hacks, its only 90% done and currently in frozen state.

Recently I reviewed the code and the stuff I struggled with is not so hard actually.
I asked ChatGPT and he basically generated me idea that I would never get from my head and its so bloody simple.

Im not sure when I will continue it.
What it needs is basically only these things to make work:
IF..ENDIF
DO_WHILE
NOT
compiling of mission scripts..

These things are implemented but not finalized, so I consider them as TODO!

Additonally I fixed many known miss2 bugs. It compiled stupid code which crashed GTA2. My compiler doesnt allow awkward things.
Always wear safety glasses while programming.
User avatar
valps
Car Jacker
Car Jacker
Posts: 32
Joined: 31 Dec 2020, 15:03
GH nick: Valps
Location: Brazil
Contact:

Re: Finally understanding IFs, WHILEs, ANDs, ORs and ELSEs

Post by valps »

I've modified the T.M.s decompiler and now it decompiles everything. It crashes if you try to decompile a entire district at once, with all missions, but all main script can be decompiled (wil.scr, bil.scr and ste.scr)

See here: viewtopic.php?p=13707#p13707
Post Reply