Jump to content

# [TUT|DOC] SCM functions (including TraversePoolEntities algorithms)

## Recommended Posts

First of all, this small tutorial has been written in hurry, thus information should be taken with a pinch of salt. It has seen the light on the occasion of some private discussions which later on I decided to share publicly. It does also apply to the other games beyond San Andreas whereon CLEO has been released and features SCM functions: command IDs make the only difference.

QUESTION

Hi,

`{\$CLEO}0000: NOPwhile true  wait 0  00A0: store_actor \$PLAYER_ACTOR position_to [email protected] [email protected] [email protected]  0AB1: call_func @getActorInSphere 4 [email protected] [email protected] [email protected] radius 5.0 handle_as [email protected]  if    [email protected] <> -1  then    // do things for this actor  endend:[email protected] = falsefor [email protected] = 0 to 30000  if  056D:  actor [email protected] defined  then    if and    803B:  not \$PLAYER_ACTOR == [email protected]    00FE:  actor [email protected] sphere 0 in_sphere [email protected] [email protected] [email protected] radius [email protected] [email protected] [email protected]    then      [email protected] = true      break    end  endendif  [email protected] == falsethen  [email protected] = -1end0AB2: ret 1 [email protected]`
Could you go more in-depth with what 0AB2 does and 0AB1? Bit confusing when [email protected] is what the result of 0AB1 is and you're using the result as a parameter for the radius further below.

I'm just confused with those two functions altogether to be honest. Do you have a better example if you have one please?

ANSWER

Doing some comparison with the other commands for which you are probably aware about their functioning, CALL_SCM_FUNC (CLEO:0AB1) is a mixture of START_NEW_SCRIPT (004F) as being a variadic command and GOSUB (0050) for the jump-and-return nature. It essentially does:

• Go to a GOSUB-like code;
• Preserve the local variables of the caller script except timers (TIMERA and TIMERB, 32-33);
• Pass a variable amount of arguments defined by the second argument;
• Copy the passed arguments to the local variables declared in the function body (in the range 0-31);
• Return zero or more values held by the respective variables appended next to the passed arguments.
RET (CLEO:0AB2) acts similarly to RETURN (0051). It specifies the values to return whose amount is defined by the first argument and returns back to the function call. After the briefly introduction about, take a look at the following sample function which computes the magnitude (length) of a 3D vector and returns the result:

`0AB1: call_scm_func @Mag 3 1.0 0.0 0.0 [email protected][...]:Mag006B: [email protected] *= [email protected]: [email protected] *= [email protected]: [email protected] += [email protected]: [email protected] *= [email protected]: [email protected] += [email protected]: sqrt [email protected] [email protected]: ret 1 [email protected]`
The step by step explanation of the function execution is shown below:
• The PC (Program Counter, formerly known as IP, Instruction Pointer) is updated to the offset of the Mag label defined by the first argument. In other words, the script jumps to the location assigned to the specified label (Mag);
• The values of the local variables of the caller script are saved temporarily ([email protected]-[email protected]);
• 3 floating-point values describing the 3D direction of the vector [1.0, 0.0, 0.0] are copied to the first 3 local variables of the function, that is [email protected], [email protected] and [email protected]. The amount of the passed arguments is determined by the second argument of CALL_SCM_FUNC (3);
• The distance formula (√(x2 + y2 + z2)) is applied and the magnitude is returned to [email protected];
• The PC is updated again by RET in order to point next to CALL_SCM_FUNC. The code returns back like if a RETURN was performed;
• The local variables of the caller script are restored with the values saved prior the function call;
• Finally, the magnitude [email protected] passed near the number of values to return by RET (1) is stored to the variable appended after the 3 function arguments of CALL_SCM_FUNC, that is [email protected].
CALL_SCM_FUNC can also be treated as a condition without being a proper conditional command (thus the not flag would affect nothing), assuming the compare flag, an internal script-dependent flag which makes conditional GOTOs such as GOTO_IF_TRUE (004E, unavailable in III/VC/SA) and GOTO_IF_FALSE (004D) deciding whether to jump otherwise, is updated before the function returns. RETURN_TRUE (00C5) and RETURN_FALSE (00C6) have been planned since III for this purpose (mostly as an extended GOSUB's feature) but got deprecated right away till they made their first practical appearance in VCS. Although no such command is available in the final release of Trilogy games and considering the custom CLEO implementation of functions which differs from the native one, we have to use some noped commands which partly emulate the same behaviour by affecting only the compare flag and setting it up to either true or false without executing the original command task (that also returns back to the caller script):
• True: IS_PC_VERSION (0485);
• False: IS_AUSTRALIAN_GAME (059A), IS_AUDIO_BUILD (0831) or IS_XBOX_VERSION (0A49).
The first and the last command has to be trusted more. If you don't mind using tricky codes, these commands will suit you nicely, otherwise you have to update the compare flag of the current script and call CRunningScript::UpdateCompareFlag (0x004859D0) manually:

`0A9F: [email protected] = current_thread_pointer0AA6: call_method 0x004859D0 [email protected] 1 0 true`
Now, imagine you wish to test for zero-length vector, you would proceed like so:

`if0AB1: call_scm_func @Mag 3 1.0 0.0 0.0 [email protected]    [...]end[...]:Mag006B: [email protected] *= [email protected]: [email protected] *= [email protected]: [email protected] += [email protected]: [email protected] *= [email protected]: [email protected] += [email protected]: sqrt [email protected] [email protected]:  not [email protected] == 0then    0485:  is_pc_versionelse    0A49:  is_xbox_versionend0AB2: ret 1 [email protected]`
As usual, ANDOR (00D6) keeps the number of the underneath conditions in a range such that they are logically conjuncted (AND, 1-8) or disjuncted (OR, 21-28):
• If non-zero, such value is increased immediately and decreased each time the compare flag is updated on the basis of the evaluation of the last condition processed and the past evaluations till reaching 0;
• If zero, the compare flag is set according to the newcomer condition evaluation as if ANDOR wasn't present, therefore whether to put it or not becomes irrelevant for single condition check.
Given that IS_PC_VERSION and IS_XBOX_VERSION are nevertheless conditions, you can discard an useless check and put NOT IS_FLOAT_LVAR_EQUAL_TO_NUMBER (8043) just before returning:

`if0AB1: call_scm_func @Mag 3 1.0 0.0 0.0 [email protected]    [...]end[...]:Mag006B: [email protected] *= [email protected]: [email protected] *= [email protected]: [email protected] += [email protected]: [email protected] *= [email protected]: [email protected] += [email protected]: sqrt [email protected] [email protected]:  not [email protected] == 00AB2: ret 1 [email protected]`
If no condition is put at the end of a function, the result of the very last (whole) check will be taken.

As regards the research of a better method for the pool traversal of T entities, I would like to show what's been sent to @Midnightz via pm:

Forget that obsolete SCM function to look through entities' pools and replace it with the following:

`:TraversePoolEntities{    Parameters:        Passed:            [email protected] - pool pointer            [email protected] - gosub label    Example:        0AB1: call_scm_func @TraversePoolEntities 2 pool_ptr 0 gosub_loop @PoolIter}0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: [email protected] += 8 // CPool<CBase, CDerived>.m_iSize0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // int iSize = pPool->m_iSize;if0019:  [email protected] > 0 // if(iSize > 0)then    000E: [email protected] -= 4 // CPool<CBase, CDerived>.m_pFlags    0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // unsigned char *pFlags = pPool->m_pFlags;    000E: [email protected] -= 1    for [email protected] = [email protected] downto 0 // for(int iID = --iSize; iID >= 0; iID--)        0A8E: [email protected] = [email protected] + [email protected]        0A8D: [email protected] = read_process_memory [email protected] size 1 vp false // unsigned char ucFlag = pFlags[iID];        if        88B7:  not test [email protected] bit 7 // if(!(ucFlag & POOL_FREE_SLOT))        then            0A90: [email protected] = [email protected] * 256            005A: [email protected] += [email protected] // unsigned int uiHandle = (iID << 8) | ucFlag;            0050: gosub [email protected] // SCM handle is [email protected] (23 free locals).        end    endend0AB2: ret 0`
Assuming you know what a SCM function is, a handle is given by:

`EnitityIndexInPool << 8 | EntityAttributes`
Using simple math, it would look like this:

`EnitityIndexInPool * 256 + EntityAttributes`
Now, let me explain every of its lines:

`0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];`
Read the real pointer where the pool is stored. For example, 0x00B74490 (CPools::ms_pPedPool) is just the variable, somehow defined globally, which holds up the pool pointer.

`000A: [email protected] += 8 // CPool<CBase, CDerived>.m_iSize0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // int iSize = pPool->m_iSize;`
Move afterwards by 8 bytes to reach the address which contains the number of allocated entities and read its content.

`if0019:  [email protected] > 0 // if(iSize > 0)then`
Check whether at least an entity is allocated or not.

`000E: [email protected] -= 4 // CPool<CBase, CDerived>.m_pFlags0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // unsigned char *pFlags = pPool->m_pFlags;`
Move backwards by 4 bytes to get the address of the first item in the flag array that specifies all attributes of allocated entities.

`000E: [email protected] -= 1for [email protected] = [email protected] downto 0 // for(int iID = --iSize; iID >= 0; iID--)`
Decrease the array size by 1 to work with 0-based indices and start iterating from the last index, which is actually the top one.

`0A8E: [email protected] = [email protected] + [email protected]: [email protected] = read_process_memory [email protected] size 1 vp false // unsigned char ucFlag = pFlags[iID];`
Retrieve the entity flag regarding the current index.

`if88B7:  not test [email protected] bit 7 // if(!(ucFlag & POOL_FREE_SLOT))then`
Verify if the least significant bit is set (the flag 0x80 probably means "free slot"). Actually, we can just check if the value is greater than or equal to 0, but we cannot do so since SCM variables occupy 4 bytes.

`0A90: [email protected] = [email protected] * 256005A: [email protected] += [email protected] // unsigned int uiHandle = (iID << 8) | ucFlag;`
Shift right the entity index by 8 bit positions (or by a single byte) and bitwise-or (add, in this case) the entity flag to obtain the entity's SCM handle.

`0050: gosub [email protected]`
Jump to the subroutine and do whatever you want with the returned handle.

`0AB2: ret 0`
Terminate the SCM function and return back to the caller.

However, TraversePoolEntities can be slightly optimized into:

`:TraversePoolEntities0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: [email protected] += 8 // CPool<CBase, CDerived>.m_iSize0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // int iSize = pPool->m_iSize;if0019:  [email protected] > 0 // if(iSize > 0)then    000E: [email protected] -= 4 // CPool<CBase, CDerived>.m_pFlags    0A8D: [email protected] = read_process_memory [email protected] size 4 vp false    000E: [email protected] -= 1    005A: [email protected] += [email protected] // unsigned char *pFlags = pPool->m_pFlags[--iSize];    for [email protected] = [email protected] downto 0 // for(int iID = iSize; iID >= 0; iID--)        0A8D: [email protected] = read_process_memory [email protected] size 1 vp false // unsigned char ucFlag = *pFlags;        if        88B7:  not test [email protected] bit 7 // if(!(ucFlag & POOL_FREE_SLOT))        then            0A90: [email protected] = [email protected] * 256            005A: [email protected] += [email protected] // unsigned int uiHandle = (iID << 8) | ucFlag;            0050: gosub [email protected] // SCM handle is [email protected] (24 free locals).        end        000E: [email protected] -= 1 // pFlags--;    endend0AB2: ret 0`
If you're looking for the shortest code which uses the minimum amount of local variables, this SCM function will suit you:

`:TraversePoolEntities0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: [email protected] += 8 // CPool<CBase, CDerived>.m_iSize0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // int iSize = pPool->m_iSize;if0019:  [email protected] > 0 // if(iSize > 0)then    000E: [email protected] -= 4 // CPool<CBase, CDerived>.m_pFlags    0A8D: [email protected] = read_process_memory [email protected] size 4 vp false    000E: [email protected] -= 1    005A: [email protected] += [email protected] // unsigned char *pFlags = pPool->m_pFlags[--iSize];    0012: [email protected] *= 256    for [email protected] = [email protected] downto 0 step 256 // for(int iShiftedID = iSize <<= 8; iShiftedID >= 0; iShiftedID -= 256)        0A8D: [email protected] = read_process_memory [email protected] size 1 vp false // unsigned char ucFlag = *pFlags;        if        88B7:  not test [email protected] bit 7 // if(!(ucFlag & POOL_FREE_SLOT))        then            005A: [email protected] += [email protected] // unsigned int uiHandle = iShiftedID | ucFlag;            0050: gosub [email protected] // SCM handle is [email protected] (27 free locals).        end        000E: [email protected] -= 1 // pFlags--;    endend0AB2: ret 0`
These are some of the pool memory addresses valid for TraversePoolEntities:

`0x00B74490 - CPool<CPed, T> *CPools::ms_pPedPool;0x00B74494 - CPool<CVehicle, T> *CPools::ms_pVehiclePool;0x00B74498 - CPool<CBuilding, T> *CPools::ms_pBuildingPool;0x00B7449C - CPool<CObject, T> *CPools::ms_pObjectPool;0x00B744A0 - CPool<CDummy, T> *CPools::ms_pDummyPool;`
CPed, CVehicle and CObject pools are the most important. CDummy may be used to allocate particles.

I hope I've been clear enough.

CLEO3
• Iterative find-next-entity search (relatively slow)

`if0AB1: call_scm_func @InitializePoolTraversal 1 pool_ref 0x00B74490 last_id_to [email protected] flags_ptr_to [email protected]    [...]end[...]0006: [email protected] = 0while 0AB1: call_scm_func @TraversePoolEntities 3 flags_ptr [email protected] first_id [email protected] last_id [email protected] next_id_to [email protected] handle_to [email protected]    if    87D6:  not [email protected] == \$PLAYER_ACTOR    then        if        0104:  actor \$PLAYER_ACTOR near_actor [email protected] radius 100.0 100.0 100.0 sphere false        then            [...]        end    endend[...]:InitializePoolTraversal{    Parameters:        Passed:            [email protected] - pool reference        Returned:            [email protected] - last entity index            [email protected] - pool flags pointer    Result:        false - empty pool    Example:        0AB1: call_scm_func @InitializePoolTraversal 1 pool_ref [email protected] last_id_to [email protected] flags_ptr_to [email protected]}0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: [email protected] += 8 // CPool<CBase, CDerived>.m_iSize0A8D: [email protected] = read_process_memory [email protected] size 4 vp false000E: [email protected] -= 1 // int iLastID = pPool->m_iSize - 1;000E: [email protected] -= 4 // // CPool<CBase, CDerived>.m_pFlags0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // unsigned char *pFlags = pPool->m_pFlags;0029:  [email protected] >= 0 // if(iLastID >= 0)0AB2: ret 2 last_id [email protected] flags_ptr [email protected]:TraversePoolEntities{    Parameters:        Passed:            [email protected] - pool flags pointer            [email protected] - first entity index            [email protected] - last entity index        Returned:            [email protected] - next entity index            [email protected] - entity handle    Result:        true - entity available    Example:        0AB1: call_scm_func @TraversePoolEntities 3 flags_ptr [email protected] first_id 0 last_id 255 next_id_to [email protected] handle_to [email protected]}// unsigned char *pFlags = (unsigned char *)aScriptParams[0];// int iFirstID = aScriptParams[1], iLastID = aScriptParams[2];005A: [email protected] += [email protected] // pFlags += iFirstID;while 801D:  not [email protected] > [email protected] // while(iFirstID <= iLastID)    0A8D: [email protected] = read_process_memory [email protected] size 1 vp false // unsigned char ucFlag = pFlags;    if    88B7:  not test [email protected] bit 7 // if(!(ucFlag & POOL_FREE_SLOT))    then        0A90: [email protected] = [email protected] * 256        000A: [email protected] += 1        005A: [email protected] += [email protected] // unsigned int uiHandle = (iFirstID++ << 8) | ucFlag;        //0485:  is_pc_version        0AB2: ret 2 next_id [email protected] handle [email protected]    end    000A: [email protected] += 1 // pFlags++;    000A: [email protected] += 1 // iFirstID++;end//0A49:  is_xbox_version0AB2: ret 2 next_id -1 handle -1`
• Iterative GOSUB (relatively fast)

`0AB1: call_scm_func @TraversePoolEntities 2 pool_ptr 0x00B74490 gosub_loop @PedIter[...]:PedIter// Entity's SCM handle is stored to [email protected]// 27 free local variables available.if87D6:  not [email protected] == \$PLAYER_ACTORthen    if    0104:  actor \$PLAYER_ACTOR near_actor [email protected] radius 100.0 100.0 100.0 sphere false    then        [...]    endend0051: return:TraversePoolEntities0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // CPool<CBase, CDerived> *pPool = *(CPool<CBase, CDerived> **)aScriptParams[0];000A: [email protected] += 8 // CPool<CBase, CDerived>.m_iSize0A8D: [email protected] = read_process_memory [email protected] size 4 vp false // int iSize = pPool->m_iSize;if0019:  [email protected] > 0 // if(iSize > 0)then    000E: [email protected] -= 4 // CPool<CBase, CDerived>.m_pFlags    0A8D: [email protected] = read_process_memory [email protected] size 4 vp false    000E: [email protected] -= 1    005A: [email protected] += [email protected] // unsigned char *pFlags = pPool->m_pFlags[--iSize];    0012: [email protected] *= 256    for [email protected] = [email protected] downto 0 step 256 // for(int iShiftedID = iSize <<= 8; iShiftedID >= 0; iShiftedID -= 256)        0A8D: [email protected] = read_process_memory [email protected] size 1 vp false // unsigned char ucFlag = *pFlags;        if        88B7:  not test [email protected] bit 7 // if(!(ucFlag & POOL_FREE_SLOT))        then            005A: [email protected] += [email protected] // unsigned int uiHandle = iShiftedID | ucFlag;            0050: gosub [email protected] // SCM handle is [email protected] (27 free locals).        end        000E: [email protected] -= 1 // pFlags--;    endend0AB2: ret 0`
CLEO4
• Iterative find-next-near-entity search (fast)

`00A0: store_actor \$PLAYER_ACTOR position_to [email protected] [email protected] [email protected]:  [email protected] = random_actor_near_point [email protected] [email protected] [email protected] radius 100.0 find_next false pass_deads falsethen    repeat        [...]    until 8AE1:  not [email protected] = random_actor_near_point [email protected] [email protected] [email protected] radius 100.0 find_next true pass_deads falseend`
Posted on Tuesday, 3 March 2015, 18:06:12 (UTC) Edited by Wesser
• 3
 012          345 678   9A   BCD EFG HIJK LMN OPQR  STUV WX    YZ
##### Share on other sites

Nice share, Wesser, have a

I noticed, that 0AB1: call_scm_func cause crash in *.cm files, can you confirm?

Edited by ZAZ
##### Share on other sites

• 2 weeks later...

Great !

I was using that "forallpeds" code and and I was having problem on Ped scanning.

With this solution, my New Umbrella script is working like a charm !

Thanks.

PS.: Is Player handle pointed by index 0 in Ped Pool ? (because my script was tweaking animation from 0 to poolSize then Player became crazy...I had to change the index range from 1 to poolSize).

Edited by pep legal
##### Share on other sites

There's a CPlayerPed structure derived from CPed and player will be in pool.

If you want to skip player, you must compare a handle value to \$PLAYER_ACTOR, which is also ped handle.

• 2

## Create an account or sign in to comment

You need to be a member in order to leave a comment

## Create an account

Sign up for a new account in our community. It's easy!

Register a new account

## Sign in

Already have an account? Sign in here.

Sign In Now
• ### 1 User Currently Viewing 0 members, 0 Anonymous, 1 Guest

×

• #### Activity

• Leaderboard
×
• Create New...

## Important Information

By using GTAForums.com, you agree to our Terms of Use and Privacy Policy.