CLEO HELP Question about file reading / counting

monday

Well-Known Member
Joined
Jun 23, 2014
Messages
1,022
Likes
66
Points
98
20
#21
maybe that's because you're allocating memory in a constant loop without releasing it

+ idk but this opcode may be messed up or I'm not using it correctly, I'm also trying to make it work at the moment and it behaves in a weird way

Edit: yeah, my bad, that read_file opcode doesn't expect pointer to be supplied, it gets the offset of variable...
 
Last edited:

monday

Well-Known Member
Joined
Jun 23, 2014
Messages
1,022
Likes
66
Points
98
20
#22
It took some time but it actually works now, it modifies the behavior of the "0A9D: readfile" opcode
Code:
{$CLEO .cs}
0000: NOP

repeat
wait 50
until 0AFA: is_samp_structures_available

call @get_strstr_address 0 _strstr_address [email protected]

while true
wait 0
   if 0C89:  key_just_pressed 49
   then
   
   gosub @read_file
   
   end
end

:read_file
if 0A9A: [email protected] = openfile "CLEO/some_file.txt" mode "rt"  // IF and SET
then

    0A9C: [email protected] = file [email protected] size
    0085: [email protected] = [email protected]
    [email protected] += 1
    alloc [email protected] [email protected]  // alloc file size + 1
    
    call @modify_0A9D_readfile_action 0 _returned_get_opcode_params_address [email protected]  // so it uses value of [email protected] instead of offset to variable [email protected]
    0A9D: readfile [email protected] size [email protected] to [email protected]
    call @restore_0A9D_readfile_action 1 saved_get_opcode_params_address [email protected]

    { 
    This was used to verify that the original behavior of the opcode is successfully restored:
    
    0A9B: closefile [email protected]
    0A9A: [email protected] = openfile "CLEO/some_file.txt" mode "rt"    
    0A9D: readfile [email protected] size 4 to [email protected]
    chatmsg "[email protected] = 0x%X" -1 [email protected]
    }


    0085: [email protected] = [email protected]
    005A: [email protected] += [email protected]  // (int)
    0A8C: write_memory [email protected] size 1 value 0 virtual_protect 0 // string terminating 0 byte
    
    chatmsg "File text = %s" -1 [email protected]
    
    alloc [email protected] 100
    format [email protected] "Michal" 
    
    0AA7: call_function [email protected] num_params 2 pop 0 string_2 [email protected] string_1 [email protected] _return [email protected] // StrStrA (it's case sensitive) - https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-strstra

    if [email protected] > 0   
    then
        chatmsg "File {00FF00}contains {FFFFFF}the following name: %s" -1 [email protected]
    else
        chatmsg "File {FF0000}doesn't {FFFFFF}contain the following name: %s" -1 [email protected]
    end    
end
return




:modify_0A9D_readfile_action
{
Function to modify:
OpcodeResult __stdcall opcode_0A9D(CRunningScript *thread)

From:
https://github.com/cleolibrary/CLEO4/blob/master/source/CCustomOpcodeSystem.cpp
(it's cleo 4.3 repository and I have 4.1 version but I guess it's the same in both)

How it looks in memory exactly:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined4 __stdcall opcode_0A9D(void)
             undefined4        EAX:4          <RETURN>
                             opcode_0A9D                                     XREF[1]:     1002214c(*)  
        10002a2f 53              PUSH       EBX   (2a2f is offset from cleo.asi)
        10002a30 56              PUSH       ESI
        10002a31 57              PUSH       EDI
        10002a32 6a 02           PUSH       0x2
        10002a34 8b f1           MOV        ESI,ECX
        10002a36 ff 15 28        CALL       dword ptr [GetScriptParams]
                 4c 02 10
        10002a3c a1 30 4c        MOV        EAX,[opcodeParams]                               = ??
                 02 10
        10002a41 8b 38           MOV        EDI,dword ptr [EAX]
        10002a43 8b 58 04        MOV        EBX,dword ptr [EAX + 0x4]
        10002a46 6a 01           PUSH       0x1
        10002a48 8b ce           MOV        ECX,ESI
        10002a4a ff 15 e8        CALL       dword ptr [GetScriptParamPointer]
                 36 02 10
        10002a50 57              PUSH       EDI
        10002a51 53              PUSH       EBX
        10002a52 6a 01           PUSH       0x1
        10002a54 50              PUSH       EAX
        10002a55 e8 86 48        CALL       _fread                                           size_t _fread(void * _DstBuf, si
                 00 00
        10002a5a 83 c4 10        ADD        ESP,0x10
        10002a5d 5f              POP        EDI
        10002a5e 5e              POP        ESI
        10002a5f 33 c0           XOR        EAX,EAX
        10002a61 5b              POP        EBX
        10002a62 c3              RET
}

if 0AA2: [email protected] = load_library "cleo.asi" // IF and SET
then
    0085: [email protected] = [email protected] // (int)


    { 
     *thread >> hFile >> size;
     
     cleo.asi + 0002a32 = 6a 02           PUSH       0x2
    }
    [email protected] += 0x2a33 // number to number of parameters (previously 2, now will be 3)
    0A8C: write_memory [email protected] size 1 value 3 virtual_protect 1
    
    
    {
    buf = GetScriptParamPointer(thread);
    
    0x2a46 6a 01           PUSH       0x1
    0x2a48 8b ce           MOV        ECX,ESI
    0x2a4a ff 15 e8        CALL       dword ptr [&GetScriptParamPointer]
             36 02 10  // last 4 bytes need to be copied
    }
    
    
    [email protected] += 0x2a4c
    0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 1
    
    [email protected] += 0x13
    for [email protected] = 0 to 9
        0085: [email protected] = [email protected] // (int)
        005A: [email protected] += [email protected]  // (int)

        0A8C: write_memory [email protected] size 1 value 0x90 virtual_protect 1 // NOP the whole GetScriptParamPointer function call
    end
    
    {
    This part shows how the parameters are loaded into registers before being supplied to fread
        10002a41 8b 38           MOV        EDI,dword ptr [EAX]
        10002a43 8b 58 04        MOV        EBX,dword ptr [EAX + 0x4]
        
    The point is to get an additional parameter into "EAX" register, because "EAX" was used to store the offset of [email protected] in opcode below:
    0A9D: readfile [email protected] size [email protected] to [email protected]
    }
    
    // overwrite the previously NOP'ed region with the following:
    // 8B 40 08 - mov eax,[eax+08]
    0A8C: write_memory [email protected] size 4 value 0x9008408B virtual_protect 1 // "90" at the end is just NOP because write_memory expects size of 1, 2 or 4 (so couldn't just write 3 bytes) 
end

ret 1 [email protected]

/* That's how it looks after modifying:

53                    - push ebx
56                    - push esi
57                    - push edi
6A 03                 - push 03 { 3 }
8B F1                 - mov esi,ecx
FF 15 284C306A        - call dword ptr [cleo.staticThreads+100C] { ->gta_sa.exe+64080 }
A1 304C306A           - mov eax,[cleo.opcodeParams] { (00A43C78) }
8B 38                 - mov edi,[eax]
8B 58 04              - mov ebx,[eax+04]
8B 40 08              - mov eax,[eax+08]
90                    - nop 
90                    - nop 
90                    - nop 
90                    - nop 
90                    - nop 
90                    - nop 
90                    - nop 
57                    - push edi
53                    - push ebx
6A 01                 - push 01 { 1 }
50                    - push eax
E8 86480000           - call cleo.CLEO_RecordOpcodeParams+2BB2
83 C4 10              - add esp,10 { 16 }
5F                    - pop edi
5E                    - pop esi
33 C0                 - xor eax,eax
5B                    - pop ebx
C3                    - ret 
*/


:restore_0A9D_readfile_action
{
  [email protected] = saved address to GetScriptParamPointer  
}
if 0AA2: [email protected] = load_library "cleo.asi" // IF and SET
then
    { 
     *thread >> hFile >> size;
     
     cleo.asi + 0002a32 = 6a 02           PUSH       0x2
    }
    [email protected] += 0x2a33 // pointer to number of retrieved parameters (overtwitten to be 3, now goes back to 2)
    0A8C: write_memory [email protected] size 1 value 2 virtual_protect 1
    
    
    {
    buf = GetScriptParamPointer(thread);
    
    0x2a46 6a 01           PUSH       0x1
    0x2a48 8b ce           MOV        ECX,ESI
    0x2a4a ff 15 e8        CALL       dword ptr [&GetScriptParamPointer]
             36 02 10 
    }
    
    [email protected] += 0x13
    0A8C: write_memory [email protected] size 4 value 0xCE8B016A virtual_protect 1
    [email protected] += 4
    0A8C: write_memory [email protected] size 2 value 0x15FF virtual_protect 1
    [email protected] += 2
    0A8C: write_memory [email protected] size 4 value [email protected] virtual_protect 1      
end
ret 0


:get_strstr_address
if 0AA2: [email protected] = load_library "shlwapi.dll" // IF and SET
then
    if 0AA4: [email protected] = get_proc_address "StrStrA" library [email protected] // IF and SET
    then
        0AB2: ret 1 [email protected]
    else
        chatmsg "{FF0000}ERROR: {FFFFFF}StrStrA couldn't be found in shlwapi.dll" -1 
    end
else
    chatmsg "{FF0000}ERROR: {FFFFFF}shlwapi.dll couldn't be loaded" -1 
end
0AB2: ret 1 0
 

monday

Well-Known Member
Joined
Jun 23, 2014
Messages
1,022
Likes
66
Points
98
20
#24
What do you mean by cleo.li? Do you mean that you use cleo 4.3 because it's the default version downloaded from cleo.li website?
 

monday

Well-Known Member
Joined
Jun 23, 2014
Messages
1,022
Likes
66
Points
98
20
#26
Here's modified version that would work with cleo 4.1.1.30 and 4.3.22
Code:
{$CLEO .cs}
0000: NOP

repeat
wait 50
until 0AFA: is_samp_structures_available

call @get_strstr_address 0 _strstr_address [email protected]

while true
wait 0
   if 0C89:  key_just_pressed 49    // key 1 this time
   then
  
   gosub @read_file
  
   end
end

:read_file
if 0A9A: [email protected] = openfile "CLEO/some_file.txt" mode "rt"  // IF and SET
then

    0A9C: [email protected] = file [email protected] size
    0085: [email protected] = [email protected]
    [email protected] += 1
    alloc [email protected] [email protected]  // alloc file size + 1
    
    call @modify_0A9D_readfile_action 0 _returned_get_opcode_params_address [email protected]  // so it uses value of [email protected] instead of offset to variable [email protected]
    0A9D: readfile [email protected] size [email protected] to [email protected]
    call @restore_0A9D_readfile_action 1 saved_get_opcode_params_address [email protected]

    {
    This was used to verify that the original behavior of the opcode is successfully restored:
    
    0A9B: closefile [email protected]
    0A9A: [email protected] = openfile "CLEO/some_file.txt" mode "rt"   
    0A9D: readfile [email protected] size 4 to [email protected]
    chatmsg "[email protected] = 0x%X" -1 [email protected]
    }


    0085: [email protected] = [email protected]
    005A: [email protected] += [email protected]  // (int)
    0A8C: write_memory [email protected] size 1 value 0 virtual_protect 0 // string terminating 0 byte
    
    chatmsg "File text = %s" -1 [email protected]
    
    alloc [email protected] 100
    format [email protected] "michal"
    
    0AA7: call_function [email protected] num_params 2 pop 0 string_2 [email protected] string_1 [email protected] _return [email protected] // StrStrA (it's case sensitive) - https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-strstra

    if [email protected] > 0   
    then
        chatmsg "File {00FF00}contains {FFFFFF}the following name: %s" -1 [email protected]
    else
        chatmsg "File {FF0000}doesn't {FFFFFF}contain the following name: %s" -1 [email protected]
    end   
end
return




:modify_0A9D_readfile_action
{
Function to modify:
OpcodeResult __stdcall opcode_0A9D(CRunningScript *thread)

From:
https://github.com/cleolibrary/CLEO4/blob/master/source/CCustomOpcodeSystem.cpp
(it's cleo 4.3 repository and I have 4.1 version but I guess it's the same in both)

How it looks in memory exactly:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined4 __stdcall opcode_0A9D(void)
             undefined4        EAX:4          <RETURN>
                             opcode_0A9D                                     XREF[1]:     1002214c(*) 
        10002a2f 53              PUSH       EBX   (2a2f is offset from cleo.asi)
        10002a30 56              PUSH       ESI
        10002a31 57              PUSH       EDI
        10002a32 6a 02           PUSH       0x2
        10002a34 8b f1           MOV        ESI,ECX
        10002a36 ff 15 28        CALL       dword ptr [GetScriptParams]
                 4c 02 10
        10002a3c a1 30 4c        MOV        EAX,[opcodeParams]                               = ??
                 02 10
        10002a41 8b 38           MOV        EDI,dword ptr [EAX]
        10002a43 8b 58 04        MOV        EBX,dword ptr [EAX + 0x4]
        10002a46 6a 01           PUSH       0x1
        10002a48 8b ce           MOV        ECX,ESI
        10002a4a ff 15 e8        CALL       dword ptr [GetScriptParamPointer]
                 36 02 10
        10002a50 57              PUSH       EDI
        10002a51 53              PUSH       EBX
        10002a52 6a 01           PUSH       0x1
        10002a54 50              PUSH       EAX
        10002a55 e8 86 48        CALL       _fread                                           size_t _fread(void * _DstBuf, si
                 00 00
        10002a5a 83 c4 10        ADD        ESP,0x10
        10002a5d 5f              POP        EDI
        10002a5e 5e              POP        ESI
        10002a5f 33 c0           XOR        EAX,EAX
        10002a61 5b              POP        EBX
        10002a62 c3              RET
}

if 0AA2: [email protected] = load_library "cleo.asi" // IF and SET
then
    0085: [email protected] = [email protected] // (int)

    if call @is_cleo_4_1 1 [email protected]
    then
    
        {
         *thread >> hFile >> size;
        
         cleo.asi + 0002a32 = 6a 02           PUSH       0x2
        }
        [email protected] += 0x2a33 // number to number of parameters (previously 2, now will be 3)
        0A8C: write_memory [email protected] size 1 value 3 virtual_protect 1
        
        
        {
        buf = GetScriptParamPointer(thread);
        
        0x2a46 6a 01           PUSH       0x1
        0x2a48 8b ce           MOV        ECX,ESI
        0x2a4a ff 15 e8        CALL       dword ptr [&GetScriptParamPointer]
                 36 02 10  // last 4 bytes need to be copied
        }
        
        
        [email protected] += 0x2a4c
        0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 1
        
        [email protected] += 0x13
        for [email protected] = 0 to 9
            0085: [email protected] = [email protected] // (int)
            005A: [email protected] += [email protected]  // (int)
    
            0A8C: write_memory [email protected] size 1 value 0x90 virtual_protect 1 // NOP the whole GetScriptParamPointer function call
        end
        
        {
        This part shows how the parameters are loaded into registers before being supplied to fread
            10002a41 8b 38           MOV        EDI,dword ptr [EAX]
            10002a43 8b 58 04        MOV        EBX,dword ptr [EAX + 0x4]
            
        The point is to get an additional parameter into "EAX" register, because "EAX" was used to store the offset of [email protected] in opcode below:
        0A9D: readfile [email protected] size [email protected] to [email protected]
        }
        
        // overwrite the previously NOP'ed region with the following:
        // 8B 40 08 - mov eax,[eax+08]
        0A8C: write_memory [email protected] size 4 value 0x9008408B virtual_protect 1 // "90" at the end is just NOP because write_memory expects size of 1, 2 or 4 (so couldn't just write 3 bytes)
    else       
        if call @is_cleo_4_3 1 [email protected]
        then       
            // backup and return pointer to GetScriptParamPointer function ([email protected])
            [email protected] += 0x26be5
            0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 1
            0085: [email protected] = [email protected] // back to cleo.asi
        
            [email protected] += 0x26BCE // param count (was 1, will be 2)
            0A8C: write_memory [email protected] size 1 value 2 virtual_protect 1
            
            0085: [email protected] = [email protected] // back to cleo.asi
            
            [email protected] += 0x26BDF
            for [email protected] = 0 to 9
                0085: [email protected] = [email protected] // (int)
                005A: [email protected] += [email protected]  // (int)
                0A8C: write_memory [email protected] size 1 value 0x90 virtual_protect 1 // NOP the whole GetScriptParamPointer function call
            end
            
            //8B 41 04 - mov eax,[ecx+4]
            0A8C: write_memory [email protected] size 4 value 0x9004418B virtual_protect 1     
        else
            chatmsg "Address of readfile opcode handler function is incorrect." -1
        end
    end
end

ret 1 [email protected]

/* That's how it looks after modifying:

53                    - push ebx
56                    - push esi
57                    - push edi
6A 03                 - push 03 { 3 }
8B F1                 - mov esi,ecx
FF 15 284C306A        - call dword ptr [cleo.staticThreads+100C] { ->gta_sa.exe+64080 }
A1 304C306A           - mov eax,[cleo.opcodeParams] { (00A43C78) }
8B 38                 - mov edi,[eax]
8B 58 04              - mov ebx,[eax+04]
8B 40 08              - mov eax,[eax+08]
90                    - nop
90                    - nop
90                    - nop
90                    - nop
90                    - nop
90                    - nop
90                    - nop
57                    - push edi
53                    - push ebx
6A 01                 - push 01 { 1 }
50                    - push eax
E8 86480000           - call cleo.CLEO_RecordOpcodeParams+2BB2
83 C4 10              - add esp,10 { 16 }
5F                    - pop edi
5E                    - pop esi
33 C0                 - xor eax,eax
5B                    - pop ebx
C3                    - ret
*/


:restore_0A9D_readfile_action
{
  [email protected] = saved address to GetScriptParamPointer 
}
if 0AA2: [email protected] = load_library "cleo.asi" // IF and SET
then
    0085: [email protected] = [email protected] // (int)

    if call @is_cleo_4_1 1 [email protected]
    then
        {
         *thread >> hFile >> size;
        
         cleo.asi + 0002a32 = 6a 02           PUSH       0x2
        }
        [email protected] += 0x2a33 // pointer to number of retrieved parameters (overtwitten to be 3, now goes back to 2)
        0A8C: write_memory [email protected] size 1 value 2 virtual_protect 1
        
        
        {
        buf = GetScriptParamPointer(thread);
        
        0x2a46 6a 01           PUSH       0x1
        0x2a48 8b ce           MOV        ECX,ESI
        0x2a4a ff 15 e8        CALL       dword ptr [&GetScriptParamPointer]
                 36 02 10
        }
        
        [email protected] += 0x13
        0A8C: write_memory [email protected] size 4 value 0xCE8B016A virtual_protect 1
        [email protected] += 4
        0A8C: write_memory [email protected] size 2 value 0x15FF virtual_protect 1
        [email protected] += 2
        0A8C: write_memory [email protected] size 4 value [email protected] virtual_protect 1     
    else
        if call @is_cleo_4_3 1 [email protected]
        then       
            [email protected] += 0x26BCE // param count (overwritten to 2, will be back to 1)
            0A8C: write_memory [email protected] size 1 value 1 virtual_protect 1
            
            0085: [email protected] = [email protected] // back to cleo.asi
            
            { Reverse to this:   
            10026bdf 6a 00           PUSH       0x0
            10026be1 8b ce           MOV        ECX,ESI
            10026be3 ff 15 60        CALL       dword ptr [DAT_10061d60]
                    1d 06 10  (last 4 bytes = GetStriptParamPointer address)
            }

            [email protected] += 0x26BDF
            0A8C: write_memory [email protected] size 4 value 0xCE8B006A virtual_protect 1
            
            [email protected] += 4
            0A8C: write_memory [email protected] size 2 value 0x15FF virtual_protect 1
            
            [email protected] += 2
            0A8C: write_memory [email protected] size 4 value [email protected] virtual_protect 1  // GetStriptParamPointer address
        else
            chatmsg "Address of readfile opcode handler function is incorrect." -1
        end   
    end
end
ret 0




:is_cleo_4_3
{
    [email protected] = cleo.asi address
}
[email protected] += 0x26BB0
0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 1
if [email protected] == 0x83EC8B55
then
    0485:  return_true
else
    059A:  return_false
end
ret 0

:is_cleo_4_1
{
    [email protected] = cleo.asi address
}
[email protected] += 0x2A2F
0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 1
if [email protected] == 0x6A575653
then
    0485:  return_true
else
    059A:  return_false
end
ret 0


:get_strstr_address
if 0AA2: [email protected] = load_library "shlwapi.dll" // IF and SET
then
    if 0AA4: [email protected] = get_proc_address "StrStrA" library [email protected] // IF and SET
    then
        0AB2: ret 1 [email protected]
    else
        chatmsg "{FF0000}ERROR: {FFFFFF}StrStrA couldn't be found in shlwapi.dll" -1
    end
else
    chatmsg "{FF0000}ERROR: {FFFFFF}shlwapi.dll couldn't be loaded" -1
end
0AB2: ret 1 0
That's extensive though, looks like a mess, and using "strstr" on the whole file would return true when you're looking for "John" if the file contains "Johnatan" (my bad, I didn't think about it before). So strcmp would have to be used for each line.

Here's what you can use:

Code:
{$CLEO .cs}
0000: NOP

repeat
wait 50
until 0AFA: is_samp_structures_available

// command that loops through names using array_size value returned from "load_names"
0B34: samp register_client_command "print_names" to_label @cmd_print_names

// command that loops through names until "0" pointer is encountered
0B34: samp register_client_command "print_names_alternative" to_label @cmd_print_names_alternative

{
    Both ways result in the same thing, both commands loop through names and print each of them.
    So it's just up to preference which method to use.
}

0B34: samp register_client_command "reload_names" to_label @cmd_reload_names

call @load_names 0 _returned_array [email protected] _array_size [email protected]


while true
wait 0

end

:cmd_print_names
0085: [email protected] = [email protected] // (int)

chatmsg "Names:" -1
for [email protected] = 1 to [email protected] // [email protected] = _array_size
    0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 0
    chatmsg "%d. %s" -1 [email protected] [email protected]

    [email protected] += 4
end
samp.CmdRet()

:cmd_print_names_alternative
0085: [email protected] = [email protected] // (int)

chatmsg "Names: {AAAAAA}(using '0' to detect the end of array)" -1
0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 0

[email protected] = 1 // counter
repeat
wait 0
    chatmsg "%d. %s" -1 [email protected] [email protected]
    [email protected] += 1
    [email protected] += 4
    0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 0
until [email protected] == 0    // this check is the reason why in "load_names" function there's: "0A8C: write_memory [email protected] size 4 value 0 virtual_protect 0"
samp.CmdRet()


:cmd_reload_names
call @release_names 1 array [email protected]
call @load_names 0 _returned_array [email protected] _array_size [email protected]
samp.CmdRet()



:load_names
{
    Usage:
    
    call @load_names 0 _returned_array [email protected] _array_size [email protected]
}
if
0AAB:   file_exists "CLEO/some_file.txt"
then
    if
    0A9A: [email protected] = openfile "CLEO/some_file.txt" mode "rt"  // IF and SET
    then
        [email protected] = 0
        alloc [email protected] 5000

        // get line count first (we could use 'realloc' function instead and do it all at once but there's no opcode for that)
        while 0AD7: read_string_from_file [email protected] to [email protected] size 5000 // retrieves data from a file into a buffer until it encounters a new line
        wait 0
            [email protected] += 1
            
            //chatmsg "[email protected] = %d" -1 [email protected]
        end

        chatmsg "Lines in file: %d" -1 [email protected]
        0085: [email protected] = [email protected] // [email protected] will be returned
        

        // [email protected] = line count, so it's the number of pointers that we will have, each pointer will store 4 bytes
        [email protected] *= 4
        [email protected] += 4
        alloc [email protected] [email protected]
        
        0085: [email protected] = [email protected] // [email protected] will be returned

        // http://ugbase.eu/index.php?threads/tutorial-using-functions-from-windows-libraries.16774/
        0A8D: [email protected] = read_memory 0x8580DC size 4 virtual_protect 0 // 0x8580DC - KERNEL32.lstrlenA
        0A8D: [email protected] = read_memory 0x8580D4 size 4 virtual_protect 0 // 0x8580D4 - KERNEL32.lstrcpyA

        0A9B: closefile [email protected]

        if 0A9A: [email protected] = openfile "CLEO/some_file.txt" mode "rt"  // IF and SET
        then
        else
            chatmsg "file couldn't be reopened for some reason (after counting its' lines)" -1
            chatmsg "terminating cleo mod" -1
            0A93: end_custom_thread // no idea if this opcode will work in scm function though
        end
        
        while 0AD7: read_string_from_file [email protected] to [email protected] size 5000 // retrieves data from a file into a buffer until it encounters a new line
        wait 0       
            0AA7: strlen_addr [email protected] num_params 1 pop 0 string [email protected] _returned_length [email protected]
            [email protected] += 1
            alloc [email protected] [email protected]
            0AA7: strcpy_addr [email protected] num_params 2 pop 0 src [email protected] dst [email protected] _ret_ignore [email protected]
            0A8C: write_memory [email protected] size 4 value [email protected] virtual_protect 0 // save pointer
            [email protected] += 4
            chatmsg "{AAAAAA}Copied string = {FFFFFF}%s" -1 [email protected]
        end 
        
        free [email protected]

        0A8C: write_memory [email protected] size 4 value 0 virtual_protect 0 // end the pointer array with 4 bytes equal to 0, this will allow to know it's the end of it
    
        0A9B: closefile [email protected]
        
        ret 2 array [email protected] array_size [email protected]
    else
    0AD1: show_formatted_text_highpriority "Couldn't open the file" 15000
    end
else
0AD1: show_formatted_text_highpriority "File doesn't exist" 15000
end
ret 2 0 0


:release_names
{
    If you use @load_names function consecutively without releasing the memory, then it will take too much of it.
   
    Input params:
    [email protected] = name_array (pointer to array of pointers to allocated memory)
   
    Usage:
    call @release_names 1 name_array [email protected]
}
0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 0
repeat
wait 0
    free [email protected]
    [email protected] += 4
    0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 0
until [email protected] == 0    // this check is the reason why in "load_names" function there's: "0A8C: write_memory [email protected] size 4 value 0 virtual_protect 0"
ret 0
 
Last edited:
OP
OP
Parazitas

Parazitas

Well-Known Member
Joined
Jan 2, 2017
Messages
1,344
Likes
143
Points
78
Location
Lithuania
Website
ugbase.eu
15
#27
Here's modified version that would work with cleo 4.1.1.30 and 4.3.22
Code:
{$CLEO .cs}
0000: NOP

repeat
wait 50
until 0AFA: is_samp_structures_available

call @get_strstr_address 0 _strstr_address [email protected]

while true
wait 0
   if 0C89:  key_just_pressed 49    // key 1 this time
   then

   gosub @read_file

   end
end

:read_file
if 0A9A: [email protected] = openfile "CLEO/some_file.txt" mode "rt"  // IF and SET
then

    0A9C: [email protected] = file [email protected] size
    0085: [email protected] = [email protected]
    [email protected] += 1
    alloc [email protected] [email protected]  // alloc file size + 1
  
    call @modify_0A9D_readfile_action 0 _returned_get_opcode_params_address [email protected]  // so it uses value of [email protected] instead of offset to variable [email protected]
    0A9D: readfile [email protected] size [email protected] to [email protected]
    call @restore_0A9D_readfile_action 1 saved_get_opcode_params_address [email protected]

    {
    This was used to verify that the original behavior of the opcode is successfully restored:
  
    0A9B: closefile [email protected]
    0A9A: [email protected] = openfile "CLEO/some_file.txt" mode "rt" 
    0A9D: readfile [email protected] size 4 to [email protected]
    chatmsg "[email protected] = 0x%X" -1 [email protected]
    }


    0085: [email protected] = [email protected]
    005A: [email protected] += [email protected]  // (int)
    0A8C: write_memory [email protected] size 1 value 0 virtual_protect 0 // string terminating 0 byte
  
    chatmsg "File text = %s" -1 [email protected]
  
    alloc [email protected] 100
    format [email protected] "michal"
  
    0AA7: call_function [email protected] num_params 2 pop 0 string_2 [email protected] string_1 [email protected] _return [email protected] // StrStrA (it's case sensitive) - https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-strstra

    if [email protected] > 0 
    then
        chatmsg "File {00FF00}contains {FFFFFF}the following name: %s" -1 [email protected]
    else
        chatmsg "File {FF0000}doesn't {FFFFFF}contain the following name: %s" -1 [email protected]
    end 
end
return




:modify_0A9D_readfile_action
{
Function to modify:
OpcodeResult __stdcall opcode_0A9D(CRunningScript *thread)

From:
https://github.com/cleolibrary/CLEO4/blob/master/source/CCustomOpcodeSystem.cpp
(it's cleo 4.3 repository and I have 4.1 version but I guess it's the same in both)

How it looks in memory exactly:

                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined4 __stdcall opcode_0A9D(void)
             undefined4        EAX:4          <RETURN>
                             opcode_0A9D                                     XREF[1]:     1002214c(*)
        10002a2f 53              PUSH       EBX   (2a2f is offset from cleo.asi)
        10002a30 56              PUSH       ESI
        10002a31 57              PUSH       EDI
        10002a32 6a 02           PUSH       0x2
        10002a34 8b f1           MOV        ESI,ECX
        10002a36 ff 15 28        CALL       dword ptr [GetScriptParams]
                 4c 02 10
        10002a3c a1 30 4c        MOV        EAX,[opcodeParams]                               = ??
                 02 10
        10002a41 8b 38           MOV        EDI,dword ptr [EAX]
        10002a43 8b 58 04        MOV        EBX,dword ptr [EAX + 0x4]
        10002a46 6a 01           PUSH       0x1
        10002a48 8b ce           MOV        ECX,ESI
        10002a4a ff 15 e8        CALL       dword ptr [GetScriptParamPointer]
                 36 02 10
        10002a50 57              PUSH       EDI
        10002a51 53              PUSH       EBX
        10002a52 6a 01           PUSH       0x1
        10002a54 50              PUSH       EAX
        10002a55 e8 86 48        CALL       _fread                                           size_t _fread(void * _DstBuf, si
                 00 00
        10002a5a 83 c4 10        ADD        ESP,0x10
        10002a5d 5f              POP        EDI
        10002a5e 5e              POP        ESI
        10002a5f 33 c0           XOR        EAX,EAX
        10002a61 5b              POP        EBX
        10002a62 c3              RET
}

if 0AA2: [email protected] = load_library "cleo.asi" // IF and SET
then
    0085: [email protected] = [email protected] // (int)

    if call @is_cleo_4_1 1 [email protected]
    then
  
        {
         *thread >> hFile >> size;
      
         cleo.asi + 0002a32 = 6a 02           PUSH       0x2
        }
        [email protected] += 0x2a33 // number to number of parameters (previously 2, now will be 3)
        0A8C: write_memory [email protected] size 1 value 3 virtual_protect 1
      
      
        {
        buf = GetScriptParamPointer(thread);
      
        0x2a46 6a 01           PUSH       0x1
        0x2a48 8b ce           MOV        ECX,ESI
        0x2a4a ff 15 e8        CALL       dword ptr [&GetScriptParamPointer]
                 36 02 10  // last 4 bytes need to be copied
        }
      
      
        [email protected] += 0x2a4c
        0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 1
      
        [email protected] += 0x13
        for [email protected] = 0 to 9
            0085: [email protected] = [email protected] // (int)
            005A: [email protected] += [email protected]  // (int)
  
            0A8C: write_memory [email protected] size 1 value 0x90 virtual_protect 1 // NOP the whole GetScriptParamPointer function call
        end
      
        {
        This part shows how the parameters are loaded into registers before being supplied to fread
            10002a41 8b 38           MOV        EDI,dword ptr [EAX]
            10002a43 8b 58 04        MOV        EBX,dword ptr [EAX + 0x4]
          
        The point is to get an additional parameter into "EAX" register, because "EAX" was used to store the offset of [email protected] in opcode below:
        0A9D: readfile [email protected] size [email protected] to [email protected]
        }
      
        // overwrite the previously NOP'ed region with the following:
        // 8B 40 08 - mov eax,[eax+08]
        0A8C: write_memory [email protected] size 4 value 0x9008408B virtual_protect 1 // "90" at the end is just NOP because write_memory expects size of 1, 2 or 4 (so couldn't just write 3 bytes)
    else     
        if call @is_cleo_4_3 1 [email protected]
        then     
            // backup and return pointer to GetScriptParamPointer function ([email protected])
            [email protected] += 0x26be5
            0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 1
            0085: [email protected] = [email protected] // back to cleo.asi
      
            [email protected] += 0x26BCE // param count (was 1, will be 2)
            0A8C: write_memory [email protected] size 1 value 2 virtual_protect 1
          
            0085: [email protected] = [email protected] // back to cleo.asi
          
            [email protected] += 0x26BDF
            for [email protected] = 0 to 9
                0085: [email protected] = [email protected] // (int)
                005A: [email protected] += [email protected]  // (int)
                0A8C: write_memory [email protected] size 1 value 0x90 virtual_protect 1 // NOP the whole GetScriptParamPointer function call
            end
          
            //8B 41 04 - mov eax,[ecx+4]
            0A8C: write_memory [email protected] size 4 value 0x9004418B virtual_protect 1   
        else
            chatmsg "Address of readfile opcode handler function is incorrect." -1
        end
    end
end

ret 1 [email protected]

/* That's how it looks after modifying:

53                    - push ebx
56                    - push esi
57                    - push edi
6A 03                 - push 03 { 3 }
8B F1                 - mov esi,ecx
FF 15 284C306A        - call dword ptr [cleo.staticThreads+100C] { ->gta_sa.exe+64080 }
A1 304C306A           - mov eax,[cleo.opcodeParams] { (00A43C78) }
8B 38                 - mov edi,[eax]
8B 58 04              - mov ebx,[eax+04]
8B 40 08              - mov eax,[eax+08]
90                    - nop
90                    - nop
90                    - nop
90                    - nop
90                    - nop
90                    - nop
90                    - nop
57                    - push edi
53                    - push ebx
6A 01                 - push 01 { 1 }
50                    - push eax
E8 86480000           - call cleo.CLEO_RecordOpcodeParams+2BB2
83 C4 10              - add esp,10 { 16 }
5F                    - pop edi
5E                    - pop esi
33 C0                 - xor eax,eax
5B                    - pop ebx
C3                    - ret
*/


:restore_0A9D_readfile_action
{
  [email protected] = saved address to GetScriptParamPointer
}
if 0AA2: [email protected] = load_library "cleo.asi" // IF and SET
then
    0085: [email protected] = [email protected] // (int)

    if call @is_cleo_4_1 1 [email protected]
    then
        {
         *thread >> hFile >> size;
      
         cleo.asi + 0002a32 = 6a 02           PUSH       0x2
        }
        [email protected] += 0x2a33 // pointer to number of retrieved parameters (overtwitten to be 3, now goes back to 2)
        0A8C: write_memory [email protected] size 1 value 2 virtual_protect 1
      
      
        {
        buf = GetScriptParamPointer(thread);
      
        0x2a46 6a 01           PUSH       0x1
        0x2a48 8b ce           MOV        ECX,ESI
        0x2a4a ff 15 e8        CALL       dword ptr [&GetScriptParamPointer]
                 36 02 10
        }
      
        [email protected] += 0x13
        0A8C: write_memory [email protected] size 4 value 0xCE8B016A virtual_protect 1
        [email protected] += 4
        0A8C: write_memory [email protected] size 2 value 0x15FF virtual_protect 1
        [email protected] += 2
        0A8C: write_memory [email protected] size 4 value [email protected] virtual_protect 1   
    else
        if call @is_cleo_4_3 1 [email protected]
        then     
            [email protected] += 0x26BCE // param count (overwritten to 2, will be back to 1)
            0A8C: write_memory [email protected] size 1 value 1 virtual_protect 1
          
            0085: [email protected] = [email protected] // back to cleo.asi
          
            { Reverse to this: 
            10026bdf 6a 00           PUSH       0x0
            10026be1 8b ce           MOV        ECX,ESI
            10026be3 ff 15 60        CALL       dword ptr [DAT_10061d60]
                    1d 06 10  (last 4 bytes = GetStriptParamPointer address)
            }

            [email protected] += 0x26BDF
            0A8C: write_memory [email protected] size 4 value 0xCE8B006A virtual_protect 1
          
            [email protected] += 4
            0A8C: write_memory [email protected] size 2 value 0x15FF virtual_protect 1
          
            [email protected] += 2
            0A8C: write_memory [email protected] size 4 value [email protected] virtual_protect 1  // GetStriptParamPointer address
        else
            chatmsg "Address of readfile opcode handler function is incorrect." -1
        end 
    end
end
ret 0




:is_cleo_4_3
{
    [email protected] = cleo.asi address
}
[email protected] += 0x26BB0
0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 1
if [email protected] == 0x83EC8B55
then
    0485:  return_true
else
    059A:  return_false
end
ret 0

:is_cleo_4_1
{
    [email protected] = cleo.asi address
}
[email protected] += 0x2A2F
0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 1
if [email protected] == 0x6A575653
then
    0485:  return_true
else
    059A:  return_false
end
ret 0


:get_strstr_address
if 0AA2: [email protected] = load_library "shlwapi.dll" // IF and SET
then
    if 0AA4: [email protected] = get_proc_address "StrStrA" library [email protected] // IF and SET
    then
        0AB2: ret 1 [email protected]
    else
        chatmsg "{FF0000}ERROR: {FFFFFF}StrStrA couldn't be found in shlwapi.dll" -1
    end
else
    chatmsg "{FF0000}ERROR: {FFFFFF}shlwapi.dll couldn't be loaded" -1
end
0AB2: ret 1 0
That's extensive though, looks like a mess, and using "strstr" on the whole file would return true when you're looking for "John" if the file contains "Johnatan" (my bad, I didn't think about it before). So strcmp would have to be used for each line.

Here's what you can use:

Code:
{$CLEO .cs}
0000: NOP

repeat
wait 50
until 0AFA: is_samp_structures_available

// command that loops through names using array_size value returned from "load_names"
0B34: samp register_client_command "print_names" to_label @cmd_print_names

// command that loops through names until "0" pointer is encountered
0B34: samp register_client_command "print_names_alternative" to_label @cmd_print_names_alternative

{
    Both ways result in the same thing, both commands loop through names and print each of them.
    So it's just up to preference which method to use.
}

0B34: samp register_client_command "reload_names" to_label @cmd_reload_names

call @load_names 0 _returned_array [email protected] _array_size [email protected]


while true
wait 0

end

:cmd_print_names
0085: [email protected] = [email protected] // (int)

chatmsg "Names:" -1
for [email protected] = 1 to [email protected] // [email protected] = _array_size
    0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 0
    chatmsg "%d. %s" -1 [email protected] [email protected]

    [email protected] += 4
end
samp.CmdRet()

:cmd_print_names_alternative
0085: [email protected] = [email protected] // (int)

chatmsg "Names: {AAAAAA}(using '0' to detect the end of array)" -1
0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 0

[email protected] = 1 // counter
repeat
wait 0
    chatmsg "%d. %s" -1 [email protected] [email protected]
    [email protected] += 1
    [email protected] += 4
    0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 0
until [email protected] == 0    // this check is the reason why in "load_names" function there's: "0A8C: write_memory [email protected] size 4 value 0 virtual_protect 0"
samp.CmdRet()


:cmd_reload_names
call @release_names 1 array [email protected]
call @load_names 0 _returned_array [email protected] _array_size [email protected]
samp.CmdRet()



:load_names
{
    Usage:
  
    call @load_names 0 _returned_array [email protected] _array_size [email protected]
}
if
0AAB:   file_exists "CLEO/some_file.txt"
then
    if
    0A9A: [email protected] = openfile "CLEO/some_file.txt" mode "rt"  // IF and SET
    then
        [email protected] = 0
        alloc [email protected] 5000

        // get line count first (we could use 'realloc' function instead and do it all at once but there's no opcode for that)
        while 0AD7: read_string_from_file [email protected] to [email protected] size 5000 // retrieves data from a file into a buffer until it encounters a new line
        wait 0
            [email protected] += 1
          
            //chatmsg "[email protected] = %d" -1 [email protected]
        end

        chatmsg "Lines in file: %d" -1 [email protected]
        0085: [email protected] = [email protected] // [email protected] will be returned
      

        // [email protected] = line count, so it's the number of pointers that we will have, each pointer will store 4 bytes
        [email protected] *= 4
        [email protected] += 4
        alloc [email protected] [email protected]
      
        0085: [email protected] = [email protected] // [email protected] will be returned

        // http://ugbase.eu/index.php?threads/tutorial-using-functions-from-windows-libraries.16774/
        0A8D: [email protected] = read_memory 0x8580DC size 4 virtual_protect 0 // 0x8580DC - KERNEL32.lstrlenA
        0A8D: [email protected] = read_memory 0x8580D4 size 4 virtual_protect 0 // 0x8580D4 - KERNEL32.lstrcpyA

        0A9B: closefile [email protected]

        if 0A9A: [email protected] = openfile "CLEO/some_file.txt" mode "rt"  // IF and SET
        then
        else
            chatmsg "file couldn't be reopened for some reason (after counting its' lines)" -1
            chatmsg "terminating cleo mod" -1
            0A93: end_custom_thread // no idea if this opcode will work in scm function though
        end
      
        while 0AD7: read_string_from_file [email protected] to [email protected] size 5000 // retrieves data from a file into a buffer until it encounters a new line
        wait 0     
            0AA7: strlen_addr [email protected] num_params 1 pop 0 string [email protected] _returned_length [email protected]
            [email protected] += 1
            alloc [email protected] [email protected]
            0AA7: strcpy_addr [email protected] num_params 2 pop 0 src [email protected] dst [email protected] _ret_ignore [email protected]
            0A8C: write_memory [email protected] size 4 value [email protected] virtual_protect 0 // save pointer
            [email protected] += 4
            chatmsg "{AAAAAA}Copied string = {FFFFFF}%s" -1 [email protected]
        end
      
        free [email protected]

        0A8C: write_memory [email protected] size 4 value 0 virtual_protect 0 // end the pointer array with 4 bytes equal to 0, this will allow to know it's the end of it
  
        0A9B: closefile [email protected]
      
        ret 2 array [email protected] array_size [email protected]
    else
    0AD1: show_formatted_text_highpriority "Couldn't open the file" 15000
    end
else
0AD1: show_formatted_text_highpriority "File doesn't exist" 15000
end
ret 2 0 0


:release_names
{
    If you use @load_names function consecutively without releasing the memory, then it will take too much of it.
 
    Input params:
    [email protected] = name_array (pointer to array of pointers to allocated memory)
 
    Usage:
    call @release_names 1 name_array [email protected]
}
0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 0
repeat
wait 0
    free [email protected]
    [email protected] += 4
    0A8D: [email protected] = read_memory [email protected] size 4 virtual_protect 0
until [email protected] == 0    // this check is the reason why in "load_names" function there's: "0A8C: write_memory [email protected] size 4 value 0 virtual_protect 0"
ret 0
Thx , also i found one more way.
IMPORTANT: This not working like snippet when you wanna return.
PHP:
if
0AAB:   file_exists "CLEO/admin.txt"
then
    if
    0A9A: [email protected] = openfile "CLEO/admin.txt" mode "rt"
    then
        [email protected] = 0
        repeat
        wait 500 // wait - before get next nick name
        [email protected]++ // proc.. to next name
        0AC8: [email protected] = allocate_memory_size 260
        0AD7: read_string_from_file [email protected] to [email protected] size 260
        0AD1: "%s" 1337 [email protected]
        until 0AD6:   end_of_file [email protected] reached  
        0A9B: closefile [email protected]
    end
end
 
Top