Natives can return arrays in PAWN!

Adding Modules to help with scripting/coding plugins.
User avatar
Archive-Poster
Posts: 1189
Gender:
United States of America

Natives can return arrays in PAWN!

Post by Archive-Poster »

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:

Code: Select all

#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:

Code: Select all

#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: Select all

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:

Code: Select all

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: Select all

<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: Select all

params[(params[0] / sizeof(cell)) + 1]
Plugin in action:
Attachments
pawn_natives_array.jpeg
pawn_natives_array.jpeg (74.55 KiB) Viewed 19 times
nativearray_amxx.zip
(37.49 KiB) Downloaded 11 times

Who is online

Users browsing this forum: No registered users and 1 guest