Hello there!
I've got something really interesting for ya!
One syntactic feature of PAWN, that I am sure almost nobody is aware of, is that natives can indeed return arrays! Try compiling the following code:
PHP Code:
native [69]returnArray();
Did it compile fine?
I knew this works for quite some time now, but I never really knew how to implement it. I've been searching on Google last year, and I really never found an answer to that. That's why today I decided to do research on my own, and I got some interesting results to show to you!
So, let's get into it. We will test this functionality using the following plugin:
PHP Code:
#include <amxmodx>
#define NAME_LENGTH 32
native [NAME_LENGTH]get_player_name(index);
public plugin_init() {
register_clcmd("say name", "cmdName");
}
public cmdName(id) {
new playerName[NAME_LENGTH];
playerName = get_player_name(id);
client_print(id, print_chat, "Your name is %s.", playerName);
client_print(id, print_chat, "Directly passed in: %s", get_player_name(id));
}
All we are going to do is print player's own name when they execute the command; very simple. The implementation of get_player_name() (and I'll post full code at the bottom) is as follows:
PHP Code:
#define NAME_LENGTH 32
static cell AMX_NATIVE_CALL get_player_name(AMX *amx, cell *params) {
const char *pName = MF_GetPlayerName(params[1]);
MF_SetAmxString(amx, params[2], pName != nullptr ? pName : "", NAME_LENGTH - 1);
return 0; // Really doesn't matter what we return, but we have to return something
}
Let's look at the assembly code of cmdName() function from our plugin, which we can get using
amxxdump:
Code:
0x90 PROC ; public cmdName(id)
0x94 BREAK ; nativetest.sma:17
0x98 BREAK ; nativetest.sma:18
; new playerName[32]
0x9C STACK 0xFFFFFF80 ; allocate 32 cells
0xA4 ZERO.pri
0xA8 ADDR.alt 0xFFFFFF80
0xB0 FILL 0x80 ; 32 cells
0xB8 BREAK ; nativetest.sma:19
0xBC ADDR.pri 0xFFFFFF80 ; playerName[32]
0xC4 PUSH.pri
0xC8 HEAP 0x80
0xD0 PUSH.alt
0xD4 PUSH.S 0xC ; id
0xDC PUSH.C 0x4
0xE4 SYSREQ.C 0x2 ; get_player_name
0xEC STACK 0x8 ; free 2 cells
0xF4 POP.pri
0xF8 POP.alt
0xFC MOVS 0x80
0x104 HEAP 0xFFFFFF80
0x10C BREAK ; nativetest.sma:21
0x110 PUSH.ADR 0xFFFFFF80 ; playerName[32]
0x118 PUSH.C 0xC0 ; "Your name is %s."
0x120 PUSH.C 0x3 ; 0x6500
0x128 PUSH.S 0xC ; id
0x130 PUSH.C 0x10
0x138 SYSREQ.C 0x3 ; client_print
0x140 STACK 0x14 ; free 5 cells
0x148 STACK 0x80 ; free 32 cells
0x150 ZERO.pri
0x154 RETN
The red colored part is our native call and assignment to playerName[] variable, specifically this line:
PHP Code:
playerName = get_player_name(id);
What we can see here is that playerName[] address gets pushed to stack, but also that the plugin allocates 0x80 bytes (0x80 = 128; 128 / 4 = 32 cells) on HEAP and pushes address of a newly allocated memory block to stack (HEAP opcode returns address in alt), parameters (index) get pushed to stack, parameter count (* 4) gets pushed to stack, and the native gets called. After that, HEAP pointer gets popped off stack to PRI, playerName[] address gets popped to ALT, and then MOVS instruction moves 32 cells from HEAP to playerName[]. Then HEAP instruction just frees previously allocated memory.
If we analyze plugin's stack at the moment our native gets called, it should look similar this:
Code:
<LOWER MEMORY ADDRESS>
argument byte count (0x4) <- params[0]
index <- params[1]
pointer to heap <- params[2], we are looking for this
playerName[] addr <- this one shouldn't matter at all to us here, it doesn't even have to exist
<HIGHER MEMORY ADDRESS>
We can see here that we need a pointer that is found in params[<param count> + 1], which is params[2] in our case. That pointer points to memory that the plugin has allocated for us to use.
If we had a native that accepts variable number of arguments (like server_print() for example), the pointer would be located at
Code:
params[(params[0] / sizeof(cell)) + 1]
Plugin in action:
Module source code attached.