{$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
Yes i use itWhat 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?
{$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
{$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.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
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