CLEO Help Question about file reading / counting

CLEO related
Status
Not open for further replies.

Parazitas

God
Staff member
Joined
Jan 2, 2017
Messages
3,315
Solutions
7
Reaction score
935
Location
Lithuania
how can i find out how many lines a text file contains without viewing it in cleo?
Should return count of lines.
 

monday

Expert
Joined
Jun 23, 2014
Messages
1,127
Solutions
1
Reaction score
158
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

Expert
Joined
Jun 23, 2014
Messages
1,127
Solutions
1
Reaction score
158
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 4@

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

:read_file
if 0A9A: 0@ = openfile "CLEO/some_file.txt" mode "rt"  // IF and SET
then

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

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


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

    if 6@ > 0   
    then
        chatmsg "File {00FF00}contains {FFFFFF}the following name: %s" -1 7@
    else
        chatmsg "File {FF0000}doesn't {FFFFFF}contain the following name: %s" -1 7@
    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: 31@ = load_library "cleo.asi" // IF and SET
then
    0085: 27@ = 31@ // (int)


    { 
     *thread >> hFile >> size;
     
     cleo.asi + 0002a32 = 6a 02           PUSH       0x2
    }
    31@ += 0x2a33 // number to number of parameters (previously 2, now will be 3)
    0A8C: write_memory 31@ 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
    }
    
    
    27@ += 0x2a4c
    0A8D: 28@ = read_memory 27@ size 4 virtual_protect 1
    
    31@ += 0x13
    for 30@ = 0 to 9
        0085: 29@ = 31@ // (int)
        005A: 29@ += 30@  // (int)

        0A8C: write_memory 29@ 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 3@ in opcode below:
    0A9D: readfile 0@ size 1@ to 3@
    }
    
    // overwrite the previously NOP'ed region with the following:
    // 8B 40 08 - mov eax,[eax+08]
    0A8C: write_memory 31@ 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 28@

/* 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
{
  0@ = saved address to GetScriptParamPointer  
}
if 0AA2: 31@ = load_library "cleo.asi" // IF and SET
then
    { 
     *thread >> hFile >> size;
     
     cleo.asi + 0002a32 = 6a 02           PUSH       0x2
    }
    31@ += 0x2a33 // pointer to number of retrieved parameters (overtwitten to be 3, now goes back to 2)
    0A8C: write_memory 31@ 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 
    }
    
    31@ += 0x13
    0A8C: write_memory 31@ size 4 value 0xCE8B016A virtual_protect 1
    31@ += 4
    0A8C: write_memory 31@ size 2 value 0x15FF virtual_protect 1
    31@ += 2
    0A8C: write_memory 31@ size 4 value 0@ virtual_protect 1      
end
ret 0


:get_strstr_address
if 0AA2: 31@ = load_library "shlwapi.dll" // IF and SET
then
    if 0AA4: 30@ = get_proc_address "StrStrA" library 31@ // IF and SET
    then
        0AB2: ret 1 30@
    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

Expert
Joined
Jun 23, 2014
Messages
1,127
Solutions
1
Reaction score
158
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

Expert
Joined
Jun 23, 2014
Messages
1,127
Solutions
1
Reaction score
158
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 4@

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

:read_file
if 0A9A: 0@ = openfile "CLEO/some_file.txt" mode "rt"  // IF and SET
then

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

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


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

    if 6@ > 0   
    then
        chatmsg "File {00FF00}contains {FFFFFF}the following name: %s" -1 7@
    else
        chatmsg "File {FF0000}doesn't {FFFFFF}contain the following name: %s" -1 7@
    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: 31@ = load_library "cleo.asi" // IF and SET
then
    0085: 27@ = 31@ // (int)

    if call @is_cleo_4_1 1 31@
    then
    
        {
         *thread >> hFile >> size;
        
         cleo.asi + 0002a32 = 6a 02           PUSH       0x2
        }
        31@ += 0x2a33 // number to number of parameters (previously 2, now will be 3)
        0A8C: write_memory 31@ 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
        }
        
        
        27@ += 0x2a4c
        0A8D: 28@ = read_memory 27@ size 4 virtual_protect 1
        
        31@ += 0x13
        for 30@ = 0 to 9
            0085: 29@ = 31@ // (int)
            005A: 29@ += 30@  // (int)
    
            0A8C: write_memory 29@ 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 3@ in opcode below:
        0A9D: readfile 0@ size 1@ to 3@
        }
        
        // overwrite the previously NOP'ed region with the following:
        // 8B 40 08 - mov eax,[eax+08]
        0A8C: write_memory 31@ 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 31@
        then       
            // backup and return pointer to GetScriptParamPointer function (28@)
            27@ += 0x26be5
            0A8D: 28@ = read_memory 27@ size 4 virtual_protect 1
            0085: 27@ = 31@ // back to cleo.asi
        
            27@ += 0x26BCE // param count (was 1, will be 2)
            0A8C: write_memory 27@ size 1 value 2 virtual_protect 1
            
            0085: 27@ = 31@ // back to cleo.asi
            
            27@ += 0x26BDF
            for 30@ = 0 to 9
                0085: 29@ = 27@ // (int)
                005A: 29@ += 30@  // (int)
                0A8C: write_memory 29@ size 1 value 0x90 virtual_protect 1 // NOP the whole GetScriptParamPointer function call
            end
            
            //8B 41 04 - mov eax,[ecx+4]
            0A8C: write_memory 27@ size 4 value 0x9004418B virtual_protect 1     
        else
            chatmsg "Address of readfile opcode handler function is incorrect." -1
        end
    end
end

ret 1 28@

/* 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
{
  0@ = saved address to GetScriptParamPointer 
}
if 0AA2: 31@ = load_library "cleo.asi" // IF and SET
then
    0085: 30@ = 31@ // (int)

    if call @is_cleo_4_1 1 31@
    then
        {
         *thread >> hFile >> size;
        
         cleo.asi + 0002a32 = 6a 02           PUSH       0x2
        }
        30@ += 0x2a33 // pointer to number of retrieved parameters (overtwitten to be 3, now goes back to 2)
        0A8C: write_memory 30@ 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
        }
        
        30@ += 0x13
        0A8C: write_memory 30@ size 4 value 0xCE8B016A virtual_protect 1
        30@ += 4
        0A8C: write_memory 30@ size 2 value 0x15FF virtual_protect 1
        30@ += 2
        0A8C: write_memory 30@ size 4 value 0@ virtual_protect 1     
    else
        if call @is_cleo_4_3 1 31@
        then       
            30@ += 0x26BCE // param count (overwritten to 2, will be back to 1)
            0A8C: write_memory 30@ size 1 value 1 virtual_protect 1
            
            0085: 30@ = 31@ // 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)
            }

            30@ += 0x26BDF
            0A8C: write_memory 30@ size 4 value 0xCE8B006A virtual_protect 1
            
            30@ += 4
            0A8C: write_memory 30@ size 2 value 0x15FF virtual_protect 1
            
            30@ += 2
            0A8C: write_memory 30@ size 4 value 0@ 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
{
    0@ = cleo.asi address
}
0@ += 0x26BB0
0A8D: 31@ = read_memory 0@ size 4 virtual_protect 1
if 31@ == 0x83EC8B55
then
    0485:  return_true
else
    059A:  return_false
end
ret 0

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


:get_strstr_address
if 0AA2: 31@ = load_library "shlwapi.dll" // IF and SET
then
    if 0AA4: 30@ = get_proc_address "StrStrA" library 31@ // IF and SET
    then
        0AB2: ret 1 30@
    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 0@ _array_size 1@


while true
wait 0

end

:cmd_print_names
0085: 31@ = 0@ // (int)

chatmsg "Names:" -1
for 30@ = 1 to 1@ // 1@ = _array_size
    0A8D: 29@ = read_memory 31@ size 4 virtual_protect 0
    chatmsg "%d. %s" -1 30@ 29@

    31@ += 4
end
samp.CmdRet()

:cmd_print_names_alternative
0085: 31@ = 0@ // (int)

chatmsg "Names: {AAAAAA}(using '0' to detect the end of array)" -1
0A8D: 30@ = read_memory 31@ size 4 virtual_protect 0

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


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



:load_names
{
    Usage:
    
    call @load_names 0 _returned_array 0@ _array_size 1@
}
if
0AAB:   file_exists "CLEO/some_file.txt"
then
    if
    0A9A: 30@ = openfile "CLEO/some_file.txt" mode "rt"  // IF and SET
    then
        29@ = 0
        alloc 31@ 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 30@ to 31@ size 5000 // retrieves data from a file into a buffer until it encounters a new line
        wait 0
            29@ += 1
            
            //chatmsg "29@ = %d" -1 29@
        end

        chatmsg "Lines in file: %d" -1 29@
        0085: 21@ = 29@ // 21@ will be returned
        

        // 29@ = line count, so it's the number of pointers that we will have, each pointer will store 4 bytes
        29@ *= 4
        29@ += 4
        alloc 28@ 29@
        
        0085: 23@ = 28@ // 23@ will be returned

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

        0A9B: closefile 30@

        if 0A9A: 30@ = 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 30@ to 31@ size 5000 // retrieves data from a file into a buffer until it encounters a new line
        wait 0       
            0AA7: strlen_addr 26@ num_params 1 pop 0 string 31@ _returned_length 25@
            25@ += 1
            alloc 24@ 25@
            0AA7: strcpy_addr 27@ num_params 2 pop 0 src 31@ dst 24@ _ret_ignore 22@
            0A8C: write_memory 28@ size 4 value 24@ virtual_protect 0 // save pointer
            28@ += 4
            chatmsg "{AAAAAA}Copied string = {FFFFFF}%s" -1 24@
        end 
        
        free 31@

        0A8C: write_memory 28@ 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 30@
        
        ret 2 array 23@ array_size 21@
    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:
    0@ = name_array (pointer to array of pointers to allocated memory)
   
    Usage:
    call @release_names 1 name_array 0@
}
0A8D: 31@ = read_memory 0@ size 4 virtual_protect 0
repeat
wait 0
    free 31@
    0@ += 4
    0A8D: 31@ = read_memory 0@ size 4 virtual_protect 0
until 31@ == 0    // this check is the reason why in "load_names" function there's: "0A8C: write_memory 28@ size 4 value 0 virtual_protect 0"
ret 0
 
Last edited:

Parazitas

God
Staff member
Joined
Jan 2, 2017
Messages
3,315
Solutions
7
Reaction score
935
Location
Lithuania
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 4@

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

   gosub @read_file

   end
end

:read_file
if 0A9A: 0@ = openfile "CLEO/some_file.txt" mode "rt"  // IF and SET
then

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

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


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

    if 6@ > 0 
    then
        chatmsg "File {00FF00}contains {FFFFFF}the following name: %s" -1 7@
    else
        chatmsg "File {FF0000}doesn't {FFFFFF}contain the following name: %s" -1 7@
    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: 31@ = load_library "cleo.asi" // IF and SET
then
    0085: 27@ = 31@ // (int)

    if call @is_cleo_4_1 1 31@
    then
  
        {
         *thread >> hFile >> size;
      
         cleo.asi + 0002a32 = 6a 02           PUSH       0x2
        }
        31@ += 0x2a33 // number to number of parameters (previously 2, now will be 3)
        0A8C: write_memory 31@ 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
        }
      
      
        27@ += 0x2a4c
        0A8D: 28@ = read_memory 27@ size 4 virtual_protect 1
      
        31@ += 0x13
        for 30@ = 0 to 9
            0085: 29@ = 31@ // (int)
            005A: 29@ += 30@  // (int)
  
            0A8C: write_memory 29@ 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 3@ in opcode below:
        0A9D: readfile 0@ size 1@ to 3@
        }
      
        // overwrite the previously NOP'ed region with the following:
        // 8B 40 08 - mov eax,[eax+08]
        0A8C: write_memory 31@ 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 31@
        then     
            // backup and return pointer to GetScriptParamPointer function (28@)
            27@ += 0x26be5
            0A8D: 28@ = read_memory 27@ size 4 virtual_protect 1
            0085: 27@ = 31@ // back to cleo.asi
      
            27@ += 0x26BCE // param count (was 1, will be 2)
            0A8C: write_memory 27@ size 1 value 2 virtual_protect 1
          
            0085: 27@ = 31@ // back to cleo.asi
          
            27@ += 0x26BDF
            for 30@ = 0 to 9
                0085: 29@ = 27@ // (int)
                005A: 29@ += 30@  // (int)
                0A8C: write_memory 29@ size 1 value 0x90 virtual_protect 1 // NOP the whole GetScriptParamPointer function call
            end
          
            //8B 41 04 - mov eax,[ecx+4]
            0A8C: write_memory 27@ size 4 value 0x9004418B virtual_protect 1   
        else
            chatmsg "Address of readfile opcode handler function is incorrect." -1
        end
    end
end

ret 1 28@

/* 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
{
  0@ = saved address to GetScriptParamPointer
}
if 0AA2: 31@ = load_library "cleo.asi" // IF and SET
then
    0085: 30@ = 31@ // (int)

    if call @is_cleo_4_1 1 31@
    then
        {
         *thread >> hFile >> size;
      
         cleo.asi + 0002a32 = 6a 02           PUSH       0x2
        }
        30@ += 0x2a33 // pointer to number of retrieved parameters (overtwitten to be 3, now goes back to 2)
        0A8C: write_memory 30@ 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
        }
      
        30@ += 0x13
        0A8C: write_memory 30@ size 4 value 0xCE8B016A virtual_protect 1
        30@ += 4
        0A8C: write_memory 30@ size 2 value 0x15FF virtual_protect 1
        30@ += 2
        0A8C: write_memory 30@ size 4 value 0@ virtual_protect 1   
    else
        if call @is_cleo_4_3 1 31@
        then     
            30@ += 0x26BCE // param count (overwritten to 2, will be back to 1)
            0A8C: write_memory 30@ size 1 value 1 virtual_protect 1
          
            0085: 30@ = 31@ // 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)
            }

            30@ += 0x26BDF
            0A8C: write_memory 30@ size 4 value 0xCE8B006A virtual_protect 1
          
            30@ += 4
            0A8C: write_memory 30@ size 2 value 0x15FF virtual_protect 1
          
            30@ += 2
            0A8C: write_memory 30@ size 4 value 0@ 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
{
    0@ = cleo.asi address
}
0@ += 0x26BB0
0A8D: 31@ = read_memory 0@ size 4 virtual_protect 1
if 31@ == 0x83EC8B55
then
    0485:  return_true
else
    059A:  return_false
end
ret 0

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


:get_strstr_address
if 0AA2: 31@ = load_library "shlwapi.dll" // IF and SET
then
    if 0AA4: 30@ = get_proc_address "StrStrA" library 31@ // IF and SET
    then
        0AB2: ret 1 30@
    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 0@ _array_size 1@


while true
wait 0

end

:cmd_print_names
0085: 31@ = 0@ // (int)

chatmsg "Names:" -1
for 30@ = 1 to 1@ // 1@ = _array_size
    0A8D: 29@ = read_memory 31@ size 4 virtual_protect 0
    chatmsg "%d. %s" -1 30@ 29@

    31@ += 4
end
samp.CmdRet()

:cmd_print_names_alternative
0085: 31@ = 0@ // (int)

chatmsg "Names: {AAAAAA}(using '0' to detect the end of array)" -1
0A8D: 30@ = read_memory 31@ size 4 virtual_protect 0

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


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



:load_names
{
    Usage:
  
    call @load_names 0 _returned_array 0@ _array_size 1@
}
if
0AAB:   file_exists "CLEO/some_file.txt"
then
    if
    0A9A: 30@ = openfile "CLEO/some_file.txt" mode "rt"  // IF and SET
    then
        29@ = 0
        alloc 31@ 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 30@ to 31@ size 5000 // retrieves data from a file into a buffer until it encounters a new line
        wait 0
            29@ += 1
          
            //chatmsg "29@ = %d" -1 29@
        end

        chatmsg "Lines in file: %d" -1 29@
        0085: 21@ = 29@ // 21@ will be returned
      

        // 29@ = line count, so it's the number of pointers that we will have, each pointer will store 4 bytes
        29@ *= 4
        29@ += 4
        alloc 28@ 29@
      
        0085: 23@ = 28@ // 23@ will be returned

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

        0A9B: closefile 30@

        if 0A9A: 30@ = 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 30@ to 31@ size 5000 // retrieves data from a file into a buffer until it encounters a new line
        wait 0     
            0AA7: strlen_addr 26@ num_params 1 pop 0 string 31@ _returned_length 25@
            25@ += 1
            alloc 24@ 25@
            0AA7: strcpy_addr 27@ num_params 2 pop 0 src 31@ dst 24@ _ret_ignore 22@
            0A8C: write_memory 28@ size 4 value 24@ virtual_protect 0 // save pointer
            28@ += 4
            chatmsg "{AAAAAA}Copied string = {FFFFFF}%s" -1 24@
        end
      
        free 31@

        0A8C: write_memory 28@ 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 30@
      
        ret 2 array 23@ array_size 21@
    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:
    0@ = name_array (pointer to array of pointers to allocated memory)
 
    Usage:
    call @release_names 1 name_array 0@
}
0A8D: 31@ = read_memory 0@ size 4 virtual_protect 0
repeat
wait 0
    free 31@
    0@ += 4
    0A8D: 31@ = read_memory 0@ size 4 virtual_protect 0
until 31@ == 0    // this check is the reason why in "load_names" function there's: "0A8C: write_memory 28@ 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: 30@ = openfile "CLEO/admin.txt" mode "rt"
    then
        10@ = 0
        repeat
        wait 500 // wait - before get next nick name
        10@++ // proc.. to next name
        0AC8: 31@ = allocate_memory_size 260
        0AD7: read_string_from_file 30@ to 31@ size 260
        0AD1: "%s" 1337 31@
        until 0AD6:   end_of_file 30@ reached  
        0A9B: closefile 30@
    end
end
 
Status
Not open for further replies.
Top