[Snippet] IfTextContains (function)

Simple but useful function which can be used to check whether text contains some phrase:

Code:
//if call @IfTextContains 2 mainTextPointer 3@ pieceToFindPointer 4@
:IfTextContains
{
    0@ - the main text
    1@ - piece of text to be found within the main text
}
 0C18: 31@ = strstr string1 0@ string2 1@
    if 31@ > 1
    then
    0485:  return_true
    else
    059A:  return_false
    end
ret 0


Example:
The code below was tested with Cleo 4.1, Samp 0.3.7 and Sampfuncs 5.3.1

Code:
{$CLEO .cs}
0000: NOP

// wait to make sure the samp chat is there

repeat
wait 50
until 0AFA: is_samp_structures_available







// prepare the data

alloc 0@ 100 // main text
format 0@ "Important message received: It's ok to be white."

alloc 1@ 100 // piece of text to check for
format 1@ "message received"

alloc 2@ 100 // piece of text that is deliberately different from the original 
format 2@ "nussugu rucuuvud"






// use the actual function

if call @IfTextContains 2 mainTextPointer 0@ pieceToFindPointer 1@
then
chatmsg "'message received' was found within the provided text" -1
end

if call @IfTextContains 2 mainTextPointer 0@ pieceToFindPointer 2@
then
//nothing
else
chatmsg "'nussugu rucuuvud' was NOT found within the provided text" -1
end




// terminate this mod

end_thread



// the actual function which can be put anywhere at the end of the script (outside of any loops/labels/codeflow)
//if call @IfTextContains 2 mainTextPointer 3@ pieceToFindPointer 4@
:IfTextContains
{
    0@ - the main text
    1@ - piece of text to be found within the main text
}
 0C18: 31@ = strstr string1 0@ string2 1@
    if 31@ > 1
    then
    0485:  return_true
    else
    059A:  return_false
    end
ret 0

Edit:
P.S.
0C18 opcode doesn't return an index, it returns a pointer to the substring, so that's why there's ">1" check. If you'd like to know at which part of the main text the phrase was found you could substract 31@ (phrase within text pointer) by the 0@ (main text pointer). You could also return 31@ from the function if you don't need the part of the text before given phrase.
Code:
if 0C18: 31@ = strstr mainTextPointer 0@ pieceToFindPointer 1@
then
chatmsg "Phrase was found within text. Pointer to it = %d" -1 31@
end
Neccessary part of the code which has to be put somewhere at the end of the script (not inside any loop):
Code:
//if 0AB1: call_scm_func @IfTextContains 2 mainTextPointer 0@ pieceToFindPointer 1@ _returnedPointer 2@
:IfTextContains
{
    0@ - the main text
    1@ - piece of text to be found within the main text
} 
0AB1: @StrLen 1 strPtr 0@ _length 31@
0AB1: @StrLen 1 strPtr 1@ _length 30@ 

//initial length check (the phrase can't be longer than the main text)
if 001D:   30@ > 31@  // (int)
then
059A:  return_false
0AB2: ret 1 0
end

31@ -= 1 
26@ = 0 // counter of the same chars in a row 
for 29@ = 0 to 31@ // for each char of the main text  (29@ = index)    
    0085: 24@ = 0@ // (int)
    005A: 24@ += 29@  // (int)
    0A8D: 28@ = read_memory 24@ size 1 virtual_protect 0
    
    0085: 23@ = 1@ // (int)
    005A: 23@ += 26@  // (int)
    0A8D: 27@ = read_memory 23@ size 1 virtual_protect 0

    if 003B:   28@ == 27@  // (int)   
    then
    26@++
        if 002D:   26@ >= 30@  // (int)
        then
        0485:  return_true
        0062: 24@ -= 30@  // (int)
        24@ += 1
        0AB2: ret 1 24@
        end
    else
        if 26@ > 0
        then
        29@-- //check the same char again and assume it's the begining of the string but only if there was matching parts before (otherwise it would go back all the time and make infinite loop)
        end
    26@ = 0 
    end           
end 
 
059A:  return_false
0AB2: ret 1 0

//0AB1: @StrLen 1 strPtr 0@ _length 31@
:StrLen
{
    0@ - string pointer
}
30@ = 0
for 31@ = 0 to 100000
0A8D: 29@ = read_memory 0@ size 1 virtual_protect 0
    if 29@ == 0
    then
    break
    else
    30@++
    0@++
    end
end 
0AB2: ret 1 30@

Usage:
Code:
if 0AB1: call_scm_func @IfTextContains 2 mainTextPointer 0@ pieceToFindPointer 1@ _returnedPointer 2@
then
//2@ is the pointer to the substring
end


Example of a full code:
Code:
{$CLEO .cs}
0000: NOP

wait 20000

{ 
    ----------------------
           PREPARATION
    ----------------------
}

0AC8: 0@ = allocate_memory_size 100
0AC8: 1@ = allocate_memory_size 100

0AD3: 0@ = format "Important message"
0AD3: 1@ = format "ant"


{ 
    ----------------------
           USAGE
    ----------------------
}

if 0AB1: call_scm_func @IfTextContains 2 mainTextPointer 0@ pieceToFindPointer 1@ _returnedPointer 2@
then
0AD1: show_formatted_text_highpriority "Text ~G~contains ~W~the phrase. Pointer: %d" time 2000 2@
chatmsg 2@ -1
wait 2000
else
0AD1: show_formatted_text_highpriority "Text ~R~doesn't contain ~w~the phrase" time 2000
wait 2000
end

0A93: end_custom_thread







{ 
    ----------------------
           FUNCTIONS 
    ----------------------
}

//if 0AB1: call_scm_func @IfTextContains 2 mainTextPointer 0@ pieceToFindPointer 1@ _returnedPointer 2@
:IfTextContains
{
    0@ - the main text
    1@ - piece of text to be found within the main text
} 
0AB1: @StrLen 1 strPtr 0@ _length 31@
0AB1: @StrLen 1 strPtr 1@ _length 30@ 

//initial length check (the phrase can't be longer than the main text)
if 001D:   30@ > 31@  // (int)
then
059A:  return_false
0AB2: ret 1 0
end

31@ -= 1 
26@ = 0 // counter of the same chars in a row 
for 29@ = 0 to 31@ // for each char of the main text  (29@ = index)    
    0085: 24@ = 0@ // (int)
    005A: 24@ += 29@  // (int)
    0A8D: 28@ = read_memory 24@ size 1 virtual_protect 0
    
    0085: 23@ = 1@ // (int)
    005A: 23@ += 26@  // (int)
    0A8D: 27@ = read_memory 23@ size 1 virtual_protect 0

    if 003B:   28@ == 27@  // (int)   
    then
    26@++
        if 002D:   26@ >= 30@  // (int)
        then
        0485:  return_true
        0062: 24@ -= 30@  // (int)
        24@ += 1
        0AB2: ret 1 24@
        end
    else
        if 26@ > 0
        then
        29@-- //check the same char again and assume it's the begining of the string but only if there was matching parts before (otherwise it would go back all the time and make infinite loop)
        end
    26@ = 0 
    end           
end 
 
059A:  return_false
0AB2: ret 1 0

//0AB1: @StrLen 1 strPtr 0@ _length 31@
:StrLen
{
    0@ - string pointer
}
30@ = 0
for 31@ = 0 to 100000
0A8D: 29@ = read_memory 0@ size 1 virtual_protect 0
    if 29@ == 0
    then
    break
    else
    30@++
    0@++
    end
end 
0AB2: ret 1 30@
 
Joined
Feb 18, 2005
Messages
2,963
Reaction score
267
RE: IfTextContains (function)

I'd rather follow this tutorial http://ugbase.eu/Thread-Tutorial-Using-functions-from-Windows-libraries
And wrap shlwapi.dll StrStr or StrStrI in a function, or roll your own, without any dependencies like SF.
 

monday

Expert
Joined
Jun 23, 2014
Messages
1,125
Reaction score
149
RE: IfTextContains (function)

I think it's more because of some peculiar kind of shame rather than because someone will actually use it but I added the code aiming to make it work without sampfuncs

(tested it with sampfuncs though :p)
 

Parazitas

God
Joined
Jan 2, 2017
Messages
3,112
Solutions
5
Reaction score
878
Location
Lithuania
Good snippet, good job @monday
I used it so many times..

Improvements:
1. Included converting to lowercase
2. Removed strlen snippet requirement

PHP:
:IfTextContains
/*
    0AC8: 1@ = allocate_memory_size 1024
    0AD3: 1@ = format "my string to compare"
    IF 0AB1: @IfTextContains 2 String1 0@ String2 1@ _Returned: Text 2@
*/
0AA7: call_function 0x718690 num_params 1 pop 1 string 0@ _Returned: length 31@ // Gta Strlen
0AA7: call_function 0x718690 num_params 1 pop 1 string 1@ _Returned: length 30@ // Gta Strlen

//initial length check (the phrase can't be longer than the main text)
IF 001D:   30@ > 31@  // (int)
THEN
    059A:  return_false
    0AB2: ret 1 0
END

31@ -= 1
26@ = 0 // counter of the same chars in a row
FOR 29@ = 0 TO 31@ // for each char of the main text  (29@ = index)
    0085: 24@ = 0@ // (int)
    005A: 24@ += 29@  // (int)
    0A8D: 28@ = readMem 24@ sz 1 vp 0

    0085: 23@ = 1@ // (int)
    005A: 23@ += 26@  // (int)
    0A8D: 27@ = readMem 23@ sz 1 vp 0
    IF AND
    28@ >= 65 // From A
    28@ >= 90 // Till Z
    THEN 28@ += 32 // convert to lowercase
    END
    IF AND
    27@ >= 65 // From A
    27@ >= 90 // Till Z
    THEN 27@ += 32 // convert to lowercase
    END
    IF 003B:   28@ == 27@  // (int)
    THEN
    26@++
        IF 002D:   26@ >= 30@  // (int)
        THEN
            0485:  return_true
            0062: 24@ -= 30@  // (int)
            24@ += 1
            0AB2: ret 1 24@
        END
    ELSE
        IF 26@ > 0
        THEN 29@-- //check the same char again and assume it's the begining of the string but only if there was matching parts before (otherwise it would go back all the time and make infinite loop)
        END
    26@ = 0
    END
END

059A:  return_false
0AB2: ret 1 0
 
Last edited:
Top