View Single Post
Author Message
klippy
AlliedModders Donor
Join Date: May 2013
Location: Serbia
Old 01-03-2016 , 08:54   Natives can return arrays in PAWN!
Reply With Quote #1

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(idprint_chat"Your name is %s."playerName);
    
client_print(idprint_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 *amxcell *params) {
    const 
char *pName MF_GetPlayerName(params[1]);

    
MF_SetAmxString(amxparams[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.
Attached Files
File Type: zip nativearray_amxx.zip (37.5 KB, 371 views)

Last edited by klippy; 06-10-2016 at 05:37.
klippy is offline