Jump to content

admin

Administrators
  • Posts

    20
  • Joined

1 Follower

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

admin's Achievements

Rookie

Rookie (2/14)

  • Conversation Starter
  • One Year In Rare
  • Week One Done
  • One Month Later

Recent Badges

0

Reputation

  1. This is a natural upgrade of the map editor i've been working on, it has several important new features: - More optimizations - Support for import and export of removebuilding generated code lines - support for quick clipboard import - support for loading & displaying COLLISION meshes (when you are puzzled why there are holes in your map - remember if you turn this on, do it before loading the map & set ambient brightness to MINIMUM. ) - Multiple object selection support (you can move things with arrow movement keys, mouse movement of multiple objects does not yet work, neither does rotation work very well.) - Improved object dragging (does not work on multiple object selection yet) To generate RemoveBuildingForPlayer lines, delete the original map objects you want gone (select and press delete) and click generate code. This will generate code for deleting the object, and it's low-detail LOD (if any) Download link: http://www.mathpudding.com/topsecretsamp/editor.rar Source code: https://github.com/JernejL/samp-map-editor New collision mode:
  2. Anti-cheat patches Objective: Southclaw and I have decided to keep a resource of anti-hack patches which can be easily integrated into your script. Every patch will follow some basic guidelines for implementation, as well as a description of the cheat which the patch prevents. Please keep in mind these are not designed to be an anti-cheat system addressing a complete system is really on an individual basis and often directly needs to be integrated into the gamemode it's self instead this patches are meant to build on your anti-cheat system by providing a means of quick implementation. Guidelines: - Patches are to be designed to be included BEFORE YSI - Patches will contain ALS hooking only - Patches will contain a callback with the following prefix OnAntiCheat ..... () ex OnAntiCheatAutoAim(playerid) - Patches will have a description outlining the anticheat - Patches can use y_iterate _________________________________________________________________________________________________________________________________________________ ___________________________________________________________________________________________________________________ Patches _________________________________________________________________________________________________________________________________________________ ___________________________________________________________________________________________________________________ Name: Autobullet Type: Animation / Weapon exploit Description: Modifies the behavior of walking weapons allowing them to be used like running weapons it also skips the reload animations. Detection Method: There is a velocity range in which this exploit happens however it is also possible to achieve the same velocity in some circumstances by using the slide-shoot-run bug that is why there is player key checks in place to make sure that does not happen. A player receiving 3 infractions will result in OnAntiCheatAutoBullet() being called. Callback: public OnAntiCheatAutoBullet(playerid, weaponid) pawn code: // Anti autobullet exploit detection by [uL]Pottus #define MAX_AUTOBULLET_INFRACTIONS 3 #define AUTOBULLET_RESET_DELAY 30 static AutoBulletInfractions[MAX_PLAYERS]; static LastInfractionTime[MAX_PLAYERS]; forward OnAntiCheatAutoBullet(playerid, weaponid); public OnPlayerWeaponShot(playerid, weaponid, hittype, hitid, Float:fX, Float:fY, Float:fZ) { if(!IsPlayerInAnyVehicle(playerid)) { switch(weaponid) { case 27, 23, 25, 29, 30, 31, 33, 24, 38: { if(CheckSpeed(playerid)) { if(gettime() - LastInfractionTime[playerid] >= AUTOBULLET_RESET_DELAY) AutoBulletInfractions[playerid] = 1; else AutoBulletInfractions[playerid]++; LastInfractionTime[playerid] = gettime(); if(AutoBulletInfractions[playerid] == MAX_AUTOBULLET_INFRACTIONS) { AutoBulletInfractions[playerid] = 0; CallLocalFunction("OnAntiCheatAutoBullet", "ii", playerid, weaponid); return 0; } } } } } if (funcidx("ACAutoB_OnPlayerWeaponShot") != -1) { return CallLocalFunction("ACAutoB_OnPlayerWeaponShot", "iiiifff", playerid, weaponid, hittype, hitid, fX, fY, fZ); } return 1; } #if defined _ALS_OnPlayerWeaponShot #undef OnPlayerWeaponShot #else #define _ALS_OnPlayerWeaponShot #endif #define OnPlayerWeaponShot ACAutoB_OnPlayerWeaponShot forward ACAutoB_OnPlayerWeaponShot(playerid, weaponid, hittype, hitid, Float:fX, Float:fY, Float:fZ); public OnPlayerDisconnect(playerid, reason) { AutoBulletInfractions[playerid] = 0; if (funcidx("ACAutoB_OnPlayerDisconnect") != -1) { return CallLocalFunction("ACAutoB_OnPlayerDisconnect", "ii", playerid, reason); } return 1; } #if defined _ALS_OnPlayerDisconnect #undef OnPlayerDisconnect #else #define _ALS_OnPlayerDisconnect #endif #define OnPlayerDisconnect ACAutoB_OnPlayerDisconnect forward ACAutoB_OnPlayerDisconnect(playerid, reason); static CheckSpeed(playerid) { new Keys,ud,lr; GetPlayerKeys(playerid,Keys,ud,lr); if(ud == KEY_UP && lr != KEY_LEFT && lr != KEY_RIGHT) { new Float:Velocity[3]; GetPlayerVelocity(playerid, Velocity[0], Velocity[1], Velocity[2]); Velocity[0] = floatsqroot( (Velocity[0]*Velocity[0])+(Velocity[1]*Velocity[1])+(Velocity[2]*Velocity[2])); if(Velocity[0] >= 0.11 && Velocity[0] <= 0.13) return 1; } return 0; } Name: Connection spoofing Type: Server exploit Description: It was possible in 0.3x to spoof a connection to a server when a player is already connected, this will result in the players name being changed to the spoofed name unprotected servers could potentially have player accounts overwritten. It is currently unknown if this exploit exists in 0.3z however it assumed that it still could potentially happen in any case this patch provides protection by preventing OnPlayerConnect() from being called to the rest of the script. IMPORTANT Make sure this is placed before any other includes that use OnPlayerConnect!!!! Detection Method: When a player connects a variable is set that they are connected, if another connection happens on the same ID while they are still connected this is considered a spoof. Callback: public OnAntiCheatPlayerSpoof(playerid) Edit - I have confirmed this as still possible. pawn code: // Anti connection spoofing patch by [uL]Pottus forward OnAntiCheatPlayerSpoof(playerid); static bool:PlayerConnected[MAX_PLAYERS]; static PlayerNames[MAX_PLAYERS][MAX_PLAYER_NAME]; public OnPlayerConnect(playerid) { // User was already connected cheat detected if(PlayerConnected[playerid]) { SetPlayerName(playerid, PlayerNames[playerid]); CallLocalFunction("OnAntiCheatPlayerSpoof", "i", playerid); return 1; } else { GetPlayerName(playerid, PlayerNames[playerid], MAX_PLAYER_NAME); PlayerConnected[playerid] = true; } if (funcidx("AntiSpoof_OnPlayerConnect") != -1) { return CallLocalFunction("AntiSpoof_OnPlayerConnect", "i", playerid); } return 1; } public OnPlayerDisconnect(playerid, reason) { PlayerConnected[playerid] = false; if (funcidx("AntiSpoof_OnPlayerDisconnect") != -1) { return CallLocalFunction("AntiSpoof_OnPlayerDisconnect", "ii", playerid, reason); } return 1; } #if defined _ALS_OnPlayerConnect #undef OnPlayerConnect #else #define _ALS_OnPlayerConnect #endif #define OnPlayerConnect AntiSpoof_OnPlayerConnect forward AntiSpoof_OnPlayerConnect(playerid); #if defined _ALS_OnPlayerDisconnect #undef OnPlayerDisconnect #else #define _ALS_OnPlayerDisconnect #endif #define OnPlayerDisconnect AntiSpoof_OnPlayerDisconnect forward AntiSpoof_OnPlayerDisconnect(playerid, reason); Name: NPC Connection spoofing Type: Server exploit Description: This is very similar to connection spoofing and was an issue in 0.3x it is assumed that it is still possible in the new SAMP version. It will result in faked NPC connections to the server. IMPORTANT Make sure this is placed before any other includes that use OnPlayerConnect!!!! Detection Method: All real NPC connections can only occur on IP: 127.0.0.1 if the IP is different then it is a spoofed NPC connection Callback: public OnAntiCheatNPCSpoof(playerid) pawn code: // Anti NPC spoof by [uL]Pottus forward OnAntiCheatNPCSpoof(playerid); public OnPlayerConnect(playerid) { if(IsPlayerNPC(playerid)) { new ip[16]; GetPlayerIp(playerid, ip, sizeof(ip)); if (!!strcmp(ip, "127.0.0.1")) { new name[MAX_PLAYER_NAME]; format(name, sizeof(name), "%i", gettime()); SetPlayerName(playerid, name); CallLocalFunction("OnAntiCheatNPCSpoof", "i", playerid); return 1; } } if (funcidx("AntiNPC_OnPlayerConnect") != -1) { return CallLocalFunction("AntiNPC_OnPlayerConnect", "i", playerid); } return 1; } #if defined _ALS_OnPlayerConnect #undef OnPlayerConnect #else #define _ALS_OnPlayerConnect #endif #define OnPlayerConnect AntiNPC_OnPlayerConnect forward AntiNPC_OnPlayerConnect(playerid); Name: Lag Troll Type: Vehicle Cheat Description: This cheat appears to send fake packets to the server in rapid succession by copying the position of the targeted vehicle the result is a lag type effect. Callback: public OnAntiCheatLagTroll(playerid) pawn code: #define MAX_VEHICLE_ID_CHANGES 5 static LastVehicleID[MAX_PLAYERS]; static VehicleIDChanges[MAX_PLAYERS]; static VehicleIDChangeTime[MAX_PLAYERS]; forward OnAntiCheatLagTroll(playerid); public OnPlayerUpdate(playerid) { new vid = GetPlayerVehicleID(playerid); if(vid > 0) { if(vid != LastVehicleID[playerid]) { if(GetTickCount() - VehicleIDChangeTime[playerid] < 5000) { VehicleIDChanges[playerid]++; if(VehicleIDChanges[playerid] > MAX_VEHICLE_ID_CHANGES) { CallLocalFunction("OnAntiCheatLagTroll", "i", playerid); return 0; } } else VehicleIDChanges[playerid] = 1; } LastVehicleID[playerid] = vid; VehicleIDChangeTime[playerid] = GetTickCount(); } if (funcidx("AntiLT_OnPlayerUpdate") != -1) { return CallLocalFunction("AntiLT_OnPlayerUpdate", "i", playerid); } return 1; } #if defined _ALS_OnPlayerUpdate #undef OnPlayerUpdate #else #define _ALS_OnPlayerUpdate #endif #define OnPlayerUpdate AntiLT_OnPlayerUpdate forward AntiLT_OnPlayerUpdate(playerid);
  3. There is a bug in SA:MP where if you press G to enter a vehicle as a passenger and the driver drives away, you run after them and after 3 seconds you get teleported in regardless of how far away the vehicle is. This include fixes that. https://www.youtube.com/watch?v=N-Gt4n8Yx9k Tested hundreds of times, seems flawless. Download: https://pastebin.com/KxU52Y1k You will need the y_hooks include which is part of YSI. All you need to do is include it in your gamemode. Nothing else is required. Let me know if you find any problems with it! Thanks.
  4. Well, this is a cool thing to add to your server. This will remove attached objects to a player as that player is aiming. Then it will return them back. It only works for the SetPlayerAttachedObject feature, not the AttachObjectToPlayer feature. //This isn't a filterscript, nor an include. You must add this to your gamemode, etc.. //Change all SetPlayerAttachedObject(...) with SetPlayerAttachedObjectE(...), except the one I have not. //Change all RemovePlayerAttachedObject(...) with RemovePlayerAttachedObjectE(...), except the one I have not. //You are done. Be nice and do good work, return0 aka Einstein Pastebin: https://pastebin.com/qcxh26XX Here is a video: https://youtube.com/watch?v=gg4vc-BMolY In order for it to work as it should, follow the instructions. Any questions or concerns? Reply here!
  5. Introduction: This script monitors the OnVehicleMod callback to check for bad vehicle mod applying, so if a bad vehicle mod is applied and that vehicle cannot use the component, it will be removed and desynced. Now version 2.0, has protection for each vehicle. Download: https://pastebin.com/C5S4U48C Credits: SA-MP, IsPlayerInInvalidNosVehicle creator, and kurta999
  6. The below table lists some common client crash addresses which you can use to help trace the problem and prevent it from occurring in future. In most cases client crashes occur because of a problem in the script. Although, it is worth mentioning that SA-MP is pretty stable nowadays. FREQUENCY ADDRESS CAUSE SOLUTION Rare 0x00000000 SA-MP is not initializing. Re-install the game, ensure singleplayer works If you have any mods installed, remove them Rare 0x006E3D17 Skin related. Often occurs when changing the skin of a player who is in a vehicle, or is just entering or exiting one. Ensure the player is on foot prior to changing their skin. Rare 0x0058370A Hard to trace. Appears to be vehicle / camera related. Occured when the script attempted to put the player in a vehicle. The vehicle the player is being teleported into is not yet available and/or rendered in the world Wait a few hundred-ms before teleporting a player into a vehicle that is newly created. Another solution would potentially be to use SetCameraBehindPlayer prior to teleporting them to the vehicle. Rare 0x0040F64C Issue in Windows 7 / Vista relating to permissions. Problem in the installer version that the SA-MP client uses Update to SA-MP 0.3d. If it still occurs rename your GTASA directory. Rare 0x0059F8B4 Occurs when the client fails to load SA-MP objects. Usually a problem with one an essential file, samp.img being missing. Re-install the client. Try running the installer as Administrator if using Windows Vista / 7. Rare 0x00746929 OR 0x0081214A Badly configured client side setting. Click Frequent 0x007F0BF7 Related to vehicle upgrades. Often caused when the server attempts to put an invalid upgrade on a vehicle (EG nos or spoilers on a motorbike). Other causes could be bad client side vehicle mods. There have been various scripts released on these forums which feature error checking for this. Frequent 0x00544BC8 Object related. Typically occurs when too many objects are showing for the client, i.e. more than what it can handle. One practical solution may be to use an object handler/streamer. Many streamers on these forums come with configuration settings to decrease the maximum amount of visible objects showing for a player at any given time Frequent 0x00415D47 Object related. Typically occurs when too many object textures are loaded for the client. Low-level problem that is hard to trace and fix. I think it's somehow related to collisions It occurs randomly depending on the object. Try to remove groups of objects and use the process of elimination to establish which objects are causing it and remove them from your mode. Frequent 0x00536DF4 Object related. Typically occurs when too many object textures are loaded for the client. See above. Frequent Chat box warning codes MESSAGE CAUSE SOLUTION Exception 0xC0000005 at 0x5E5815 Difficult to trace. The method this address points to does a whole load of things. It processes animation blending based on the surface the ped is standing on and then deals with audio, and is called right after a function which *gives* you weapons... Perhaps what happened here is that some script-invoked event occurred right at the moment that you were entering a vehicle (e.g. receiving a weapon, teleporting or something similar). - Exception 0x0000005 at 0x534134 Issue with Windows 7 / Vista Access levels Running SA-MP as administrator appear to fix it. Exception 0xC0000005 at 0x544BC8 See 0x00544BC8 See 0x00544BC8 Exception 0xC0000005 at 0x536DF4 See 0x00544BC8 See 0x00544BC8 Exception 0xC0000005 at 0x7F120E Invalid vehicle upgrade applied Click
  7. Information contained in the SA-MP server structures are designed to be accessed using pawn, not by peeking and poking the server memory. Plugins are designed to extend pawn. If there is information that cannot be accessed by scripting, people should request it added to SA-MP's pawn scripting natives. 90% of the information held by the server can already be accessed using pawn. Memory addresses and structures within the SA-MP server change with every release, pawn natives do not. If a plugin is written to access specific memory addresses backward compatibility for server owners will be broken with every server update.
  8. Navigation Introduction This is not a C/++ help forum FAQ Quick questions Getting started Examining the code The module definition file What is "EXPORTS" Exported functions Defines and structures Amx functions Amx function examples Registering natives Getting a string and its length Alternative way to get a string How to set a string Casting and returning floats Passing parameters by reference Getting and modding array values Call a callback Calling natives and hooking callbacks Invoke Getting started Adding SA-MP natives Calling natives Hooking callbacks Closing Special thanks Introduction I decided to create this topic to address issues and questions that are frequently asked about plugin development. I'm no where near an expert when it comes to C/++, or the plugin SDK, i am simply sharing what i have come to learn over my time of developing plugins. Hopefully once this topic is done it will become more of a group project with other users contributing information that i might not know, or have forgotten to include. This is a pretty big topic to try and cover, so i will need assistance. This is not a C/++ help forum People seem to be very confused when it comes to this post. I've seen a lot of people ask completely valid questions regarding the plugin SDK who end up with multiple "This is not a C/++ help forum!" quotes. Just because someone is expected to know the languages doesn't mean they're not allowed to ask questions about the actual SDK! There is a difference between asking what a pointer is, and asking something like how to create callbacks! With that being said there are excellent sites and forums dedicated to teaching and answering questions about C/++. Here are a few that i have used: These sites have excellent tutorials to learn from: cprogramming.com cplusplus.com learncpp.com If you get stuck on a C/++ issue you can use one of these forums to ask for assistance: dreamincode.net stackoverflow.com FAQ Quick questions Question: How are plugins made? Answer: Plugins are made in C/++ using the plugin SDK. This FAQ assumes you know C/++ already, and will provide some information on the actual SDK later on in the article. Question: Can i create a plugin in a programming language other than C/++? Answer:It's possible in theory. There have been attempts at porting the SA-MP plugin SDK to the programming language D, which supports C's application binary interface. While I've never seen a fully working plugin written in D, this example has shed light on the possibility of writing plugins in languages that support C/++. There also has been talk about the possibility of bindings, but i've yet to see any backing on this. Question: How can i get my plugin to be multiplatform (Available on linux and windows)? Answer: Linux and windows are two different operating systems which both have their own differing API's and implementations. Getting your code to work on both platforms means you have to use platform independent code or a sort of framework that handles everything having to do with platforms FOR you. After you're sure your code doesn't rely on any one API, you simply have to compile your code in your desired environment (once again, this guide assumes you know how to do this). Question: Can my plugin use memory hacking? Answer: To be honest this is a sort of grey area. Using memory hacking to hook callbacks or call functions seems to be completely acceptable. Anything that modifies the servers memory seems to be off limits (See This). If your planing on making a plugin that needs to use memory hacking for anything other callback hooking or function calling, ask permission before hand and get approval before posting; That honestly is the best way to find out! Question: Is it possible to make a plugin that ______ ? Answer: In most cases any idea is completely possible to implement. It may require a stand alone application that communicates to the plugin, but its likely possible. Questions like these should not be posted. You heard the answer, now move on! This is not a plugin request section. Hopefully there will be a day when there is enough plugin creators to have a section or sticky topic for requests (This guide is designed to introduce and help new plugin developers, so lets hope). Question: What IDE/Compiler should i use for ______ ? Answer: That is completely up to you! I personally will be using VC++ 2010 express as my windows IDE and Compiler, and plan on using g++ for my Linux compiler. Just mess around with different options and see which one you like the best! Question: What is a module definition file (.def)? Answer: A module definition file is a special file in the Visual Studio IDE that provides the linker with information about the program that's being linked. When it comes to writing plugins for SA-MP we only really use the "EXPORTS" statement which provides information about our exported functions. We'll be looking at this statement later in the article. Question: When i release a plugin, does it have to have the source with it? Answer: If you're releasing something on this forum it always has to have the source with it. Plugins are no exception (unless the plugin is approved by a developer to be posted without a source - maybe an anti-cheat?). Getting started You would be shocked how many times i get asked "How do i get started with plugin development?". I decided to add this entire section to answer that question! Just because you know the language doesn't mean you know your way around the IDE after all; I think that's especially true when it comes to VC++ 2010. Visual studio can be a very intimidating place to new users. I hardly think its fair to just hand you the plugin SDK without showing you around first! Messing with property pages/the linker can easily scare people away, so I'm going to guide you through it. Note: If you chose to use a different IDE/compiler that targets Windows, you're out of luck! It seems that the only way to successfully export functions is to use a module definition file (.DEF). I've tried to include an alternative method in this article using __declspec(dllexport), but this simply didn't work due to the __stdcall calling convention mangling the exported function's names (See: Here). Here are the downloads you'll need for this section: Visual C++ 2010 express: Free download Plugin SDK (Plain): Download The first thing we want to do is create a new project. Select file->new->project to do just that. Once you create a new project its going to ask you what type of project you're creating. Select Win32 project, enter a project name, and press OK to continue. Once your project setting are taken care of this dialog should appear. Press next to continue. After you clicked continue you should see this dialog popup asking for your application type and settings. For the type select DLL (Dynamic-link library) and for the settings select empty project. Once you're done press the finish button at the bottom. The next thing we want to do is go to our solution explorer. The solution explorer is normally on the left side of the IDE. If you accidentally disabled it you can enable it once again by pressing CTRL+ALT+L or by selecting view->other windows->solution explorer. Once you find the solution explorer right click the project name (In this example its "Test") and select properties. Once you see the property pages navigate to Configuration properties->Linker->Input on the left side. Once you're there you want to add a module definition file. You can name this file anything you like as long as it has a ".def" extension at the end of it; People normally name def files after their project. Once you've named your definition file press OK. Now all we have to do is add our files (this includes our definition file!). To add a file to your project navigate to the solution explorer again, right click the project name, click add, and then select new item. In this example we'll be adding our definition file. Since there isnt an option for definition files, just pick a source file (.cpp) and type the name of the module definition file you decided on (Note: Make sure you include the ".def" extension, other wise a ".cpp" extension will be added!). After this is done add a source file for the project the same way (except dont add an extension). Most people use the name "Main" for there main source file. Before we can start adding anything to our two currently empty files, we have to actually add the SDK to the project. If you haven't already, download the SDK from the link that was provided in the beginning of this section. Once its downloaded, extract the SDK folder into your current project's directory. Note This part is optional: I personally like to create filters to organize all our files and code. A filter is an organizational tool in visual studio that creates folders in your project, but doesn't create corresponding Windows folders (its recommended to create matching window folders). To create a filter right click your project in the solution explorer->add->New Filter and select a name for your filter (Name it SDK in this case). The next step is to add all the SDK files to our current project. To add existing files to a project simply right click the folder or project you wish to add them to and select add -> Existing item. You'll want to add everything that is inside the SDK folder in your project directory (Note: You can select multiple files by holding Ctrl while clicking the files to add). For folder consistency we're gonna create another filter inside the SDK filter, and name it amx. You'll of course want to add all the contents of the SDK\amx\ folder into the amx filter. Now it's time to get this plugin compiled! You should have a module definition file and a source file open if you followed the instructions correctly; If not, go back a few steps and follow the instructions carefully. Go ahead and copy and paste the following information into their corresponding files. Don't worry if you don't understand any of it, we'll be addressing it all in the next section. Source file (*.cpp): pawn Code: #include "SDK\amx\amx.h" #include "SDK\plugincommon.h" typedef void (*logprintf_t)(char* format, ...); logprintf_t logprintf; extern void *pAMXFunctions; cell AMX_NATIVE_CALL HelloWorld(AMX* amx, cell* params) { logprintf("This was printed from the Test plugin! Yay!"); return 1; } PLUGIN_EXPORT unsigned int PLUGIN_CALL Supports() { return SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES; } PLUGIN_EXPORT bool PLUGIN_CALL Load(void **ppData) { pAMXFunctions = ppData[PLUGIN_DATA_AMX_EXPORTS]; logprintf = (logprintf_t) ppData[PLUGIN_DATA_LOGPRINTF]; logprintf(" * Test plugin was loaded."); return true; } PLUGIN_EXPORT void PLUGIN_CALL Unload() { logprintf(" * Test plugin was unloaded."); } AMX_NATIVE_INFO PluginNatives[] = { {"HelloWorld", HelloWorld}, {0, 0} }; PLUGIN_EXPORT int PLUGIN_CALL AmxLoad( AMX *amx ) { return amx_Register(amx, PluginNatives, -1); } PLUGIN_EXPORT int PLUGIN_CALL AmxUnload( AMX *amx ) { return AMX_ERR_NONE; } Module definition file (*.def) Code: EXPORTS Supports Load Unload AmxLoad AmxUnload Examining the code In this section we'll be taking a look at some of the defines, structures, and functions that the SA-MP plugin SDK has to offer. You should have a comfortable understanding of C/++ before proceeding, as i will just be explaining SDK related information once we hit the coded segments. From here on out, there wont be any hand holding! The module definition file Before we take a look at the code, we're gonna check out the module definition file we created in the previous section. First of all, what exactly is a module definition file? We know its a visual studio exclusive feature, but what exactly does it do? Its simple! A module definition file provides information to the linker about the code that is being linked. There are a lot of rules and statements that can be used in these files, but we're just gonna talk about one, the EXPORTS statement. What is "EXPORTS"? Exports is a statement that allows us to... well, export stuff to our application! Why do we do this? Because we have to, its as simple as that. The functions we export are entry points in our DLL file. Applications can only touch the functions in our DLL that we export; if we don't export them they remain private to the DLL file. We don't want that now, do we? No. So we export everything that that application is going to be directly accessing. Exported functions There are currently 6 functions that need to be exported. We use 5 of them inside of our project that we've been setting up. Don't worry about the PLUGIN_EXPORT, and PLUGIN_CALL definitions you see in the function declarations. We will be covering these along with some other important definitions (marked by green text) later in the article. Supports() This function tells the server what capabilities our plugin will have based on what it returns. Generally we only use 3 support flags in plugins: SUPPORTS_VERSION, SUPPORTS_AMX_NATIVES, and SUPPORTS_PROCESS_TICK. Load(void**) The Load function is pretty straight forward. This is called when the plugin is loaded and gets passed an array of addresses that the plugin will use to function. The two indexes we typically use are PLUGIN_DATA_AMX_EXPORTS, and PLUGIN_DATA_LOGPRINTF. Unload() Unload is called when the plugin is unloaded (server is shutdown). AmxLoad(AMX*) This is called when a new AMX instance is loaded into the server. This will be called for every filterscript/gamemode! Because of this it isn't a good idea to store a single AMX instance for the entire plugin, instead use a queue/list/vector. In this function we also register our custom native functions we wish to provide PAWN with. AmxUnload(AMX*) This function is called when ever an AMX instance is unloaded. If you store AMX instances, make sure you remove them. Otherwise you'll have instances to non-existing gamemodes/filterscripts. ProcessTick() ProcessTick is a function that gets called on every iteration of the server's loop. People generally use this function as a method of managing time by keeping track of the amount of ticks that have passed. The SA-MP server is said to have a sleep time of 5ms, so if 50 ticks go by you have an idea of the elapsed time (5 * 50 = 250ms). Note: Anyone who uses threads in their plugins and require PAWN interaction needs to use this function to ensure PAWN isnt busy doing another task! Defines and structures Now comes the fun part, we get to take a look at the code! You'll probably notice right off the bat that we're using a ton of defines and structures through our little example from the previous section. If you haven't ever developed a SA-MP plugin before, these can appear very confusing and even intimidating. We'll be shedding some light on these defines and structures in this section. cell A "cell" is a typedef that is simply there for portability. PAWN offers support for 16bit, 32bit, and 64bit integers. The typedef "cell" will always be the correct size while "int" might not be. Generally with SA-MP plugins, this typedef will always be a 32bit integer. Note: There is also a "ucell" typedef for unsigned integers, but this is hardly used. AMX_NATIVE_CALL This defines the calling convention that our native functions will use. At the moment its defined to nothing, so the default will be used. AMX The name of this structure should be pretty clear, its an AMX instance. This structure has tons of information relating to the data segments along with tons of other amx related information. This structure must be present in all native function declarations along with a cell pointer to the passed parameters; Without this info we dont know what script is calling our native, nor do we know what was passed. PLUGIN_EXPORT This is defined as "PLUGIN_EXTERN_C". PLUGIN_EXTERN_C If using a C++ compiler this is defined as "extern "C"". This is for compatibility with C. C++ offers things like function overloading so additional information like the number/size of arguments is stored with the function's name, this is refered to as "name mangling". When this define is used, it tells C++ to use C-style linkage and thus avoids C++ name mangling. PLUGIN_CALL This define tells C/++ what calling convention to use for our exported functions. If you use a compiler targeting windows, this is defined as __stdcall. Otherwise its defined as nothing and the default convention is used. SUPPORTS_VERSION This define is to be used in a bit mask that is returned by our "Supports()" function. This flag is used to check for compatibility with the server. SUPPORTS_AMX_NATIVES This is yet another define that is to be used by our "Supports()" function. Any plugin that uses AMX functions must use this flag! Without this flag you'll get a run time 19 error due to your natives not registering with the server (amx_Register). SUPPORTS_PROCESS_TICK Our last flag for our "Supports()" function. If you're going to be using the "ProcessTick()" function, you have to add this to our "Supports()" function's returned bit mask. PLUGIN_DATA_AMX_EXPORTS This is used as an index to the multidimensional array that gets passed on Load. This particular index holds the AMX function table. All plugins must use this index to assign the address of the function table to pAMXFunctions. PLUGIN_DATA_LOGPRINTF Yet another index that is to be used with the multidimensional array that gets passed on Load. This index holds the address of the logprintf function which prints information and saves said information to the server's log file. If your plugin uses this function you must use this index to assign the address to the logprintf function pointer. AMX_NATIVE_INFO This struct is used in conjunction with amx_Register. It contains a string that holds your new native's name, and a pointer to it's address. There are a few other important definitions that relate to AMX function errors, but they're already documented inside of the amx header. So instead of reinventing the wheel, I'm just going to post the enum with all the error codes and their corresponding comments. Note: Every amx function, with the exception of amx_NativeInfo, returns one of these error codes if a problem was encountered. pawn Code: enum { AMX_ERR_NONE, /* reserve the first 15 error codes for exit codes of the abstract machine */ AMX_ERR_EXIT, /* forced exit */ AMX_ERR_ASSERT, /* assertion failed */ AMX_ERR_STACKERR, /* stack/heap collision */ AMX_ERR_BOUNDS, /* index out of bounds */ AMX_ERR_MEMACCESS, /* invalid memory access */ AMX_ERR_INVINSTR, /* invalid instruction */ AMX_ERR_STACKLOW, /* stack underflow */ AMX_ERR_HEAPLOW, /* heap underflow */ AMX_ERR_CALLBACK, /* no callback, or invalid callback */ AMX_ERR_NATIVE, /* native function failed */ AMX_ERR_DIVIDE, /* divide by zero */ AMX_ERR_SLEEP, /* go into sleepmode - code can be restarted */ AMX_ERR_INVSTATE, /* invalid state for this access */ AMX_ERR_MEMORY = 16, /* out of memory */ AMX_ERR_FORMAT, /* invalid file format */ AMX_ERR_VERSION, /* file is for a newer version of the AMX */ AMX_ERR_NOTFOUND, /* function not found */ AMX_ERR_INDEX, /* invalid index parameter (bad entry point) */ AMX_ERR_DEBUG, /* debugger cannot run */ AMX_ERR_INIT, /* AMX not initialized (or doubly initialized) */ AMX_ERR_USERDATA, /* unable to set user data field (table full) */ AMX_ERR_INIT_JIT, /* cannot initialize the JIT */ AMX_ERR_PARAMS, /* parameter error */ AMX_ERR_DOMAIN, /* domain error, expression result does not fit in range */ AMX_ERR_GENERAL, /* general error (unknown or unspecific error) */ }; Amx functions The amx functions are very well documented documented inside of the PAWN implementers guide, which can be found here. Instead of reinventing the wheel and explaining all the functions in my own words, im simply going to copy them here from the implementers guide, and make them a bit more presentable; I believe this will make the content much more accessible. With that said, this section will be mostly a reference for the next section which is focused on something that the implementers guide lacks, example code. Note: I will only be covering the functions that i think are vital for plugins (for now). Anything else can be found in the implementers guide, just make sure you use the version i linked to in this section! Key: Available in this guide Not available amx_Allot - Reserve heap space in the abstract machine Syntax: Code: amx_Allot(AMX *amx, cells, cell *amx_addr, cell **phys_addr); amx The abstract machine. cells The number of cells to reserve. amx_addr The address of the allocated cell as the pawn program (that runs in the abstract machine) can access it. phys_addr The address of the cell for C/C++ programs to access. Notes: In earlier releases of pawn, arrays and strings had to be passed to a script after explicitly allocating memory for it on the amx stack. In the current release, this functionality has been largely replaced by the functions amx_PushArray and amx_PushString. A pawn function can only access memory inside its abstract ma- chine. If a parameter is to be passed “by reference” to a pawn function, one must pass the address of that parameter to amx_Exec. In addition, that address itself must be within the address range of the abstract machine too. An added complexity is that the abstract machine uses addresses that are relative to the data section of the abstract machine, and the host program uses address relative to the environment that the operating system gives it. amx_Allot allocates memory cells inside the abstract machine and it returns two addresses. The amx_addr parameter is the address of the variable relative to the “data section” of the abstract machine; this is the value you should pass to amx_Exec (via amx_Push). Pa- rameter phys_addr holds the address relative to the host program’s address space. So a C/C++ program can use this address and write into the allocated memory. After amx_Exec returns, you may inspect the memory block (the pawn function called by amx_Exec may have written into it) and finally release it by calling amx_Release. See also: amx_Exec, amx_PushArray, amx_PushString, amx_Release amx_ctof - Cast “cell” to “float” Syntax: Code: [] amx_ctof([cell] c); c The value to cast from “cell” type to “float”. Returns: The same bit pattern, but now as a floating point type. Notes: This macro casts a “cell” type into a “float” type without changing the bit pattern. A normal type cast in C/C++ changes the memory representation of the expression so that its numeric value in IEEE 754 format comes closest to the original integer value. The pawn parser and abstract machine store floating point values in a cell — when retrieving a floating point value from a cell, the bit pattern must not be changed. See also: amx_ftoc amx_Exec - Run code Syntax: Code: amx Exec(AMX *amx, *retval, index); amx The abstract machine from which to call a function. retval Will hold the return value of the called function uponreturn. This parameter may be NULL if you are notinterested in the return value. index An index into the “public function table”; it indicatesthe function to execute. See amx_FindPublic formoreinformation. Use AMX_EXEC_MAIN to start executingat the main function, and AMX_EXEC_CONT to continueexecution from a “sleep state”. Notes: This function runs the script, starting at the indicated function. It calls the callback function for any native function call that the code in the amx makes. amx_Exec assumes that all native functions are correctly initialized with amx_Register. See also: amx_FindPublic, amx_Register amx_FindPublic - Return the index of a public function Syntax: Code: amx FindPublic(AMX *amx, *funcname, *index); amx The abstract machine. funcname The name of the public function to find. index Upon return, this parameter holds the index of therequested public function. See also: amx_Exec, amx_FindNative, amx_FindPubVar, amx_GetPublic, amx_NumPublics amx_ftoc - Cast “float” to “cell” Syntax: Code: [cell] amx_ftoc(] f); f The value to cast from “float” type to “cell”. Returns: The same bit pattern, but now as a “cell” type. Notes: This macro casts a “float” type into a “cell” type without changing the bit pattern. A normal type cast in C/C++ changes the memory representation of the expression so that its numeric value in integer format is the integral (truncated) value of the original rational value. The pawn parser and abstract machine store floating point values in a cell —when storing a floating point value in a cell, the bit pattern must not be changed. See also: amx_ctof amx_GetAddr - Resolve an AMX address Syntax: Code: amx_GetAddr(AMX *amx,cell amx_addr,cell **phys_addr); amx The abstract machine. amx_addr The address relative to the abstract machine. phys_addr A pointer to the variable that will hold the memoryaddress of the indicated cell. If the amx_addr parame-ter is not a valid address inside the abstract machine,phys_addr will be set to NULL. Notes: This function returns the memory address of an address in the ab- stract machine. One typically uses this function in an extension module, because it allows you to access variables inside the abstract machine. amx_GetString - Retrieve a string from the abstract machine Syntax: Code: amx_GetString( *dest, cell *source, use_wchar, size_t size); dest A pointer to a character array of sufficient size to holdthe converted source string. source A pointer to the source string. Use amx_GetAddr toconvert a string address in the amx to the physicaladdress. use_wchar A non-zero value interprets the dest argument as apointer to “wide characters” —i.e. wchar_t, regard-less of its char type. This allows the function to storeUnicode strings. size The maximum number of characters to store in dest,including the terminating zero byte. If the string inthe source is longer, the string in dest will be trun-cated. Notes: This function converts both packed strings and unpacked strings from the “pawn” format to the “C” format. When retrieving an unpacked string with parameter use_wchar set to zero, the function may truncate characters from wide characters to 8-bit ASCII/ANSI. See also: amx_SetString amx_Push - Pass a numeric argument “by-value” Syntax: Code: amx_Push(AMX *amx, cell value); amx The abstract machine. value The value to pass to the public function. Notes: Any parameters to a public function must be pushed to the func- tion before calling amx_Exec. If a public function has multiple ar- guments, the arguments must be pushed int reverse order. See also: amx_Exec, amx_PushArray, amx_PushString amx_PushArray - Pass an argument or array “by-reference” Syntax: Code: amx_PushArray(AMX *amx, cell *amx_addr, cell **phys_addr, cell array[], numcells); amx The abstract machine. amx_addr The address of the allocated cell as the pawn program (that runs in the abstract machine) can access it, needed to release the memory block. This parameter may be NULL. phys_addr The address of the cell for C/C++ programs to access. This parameter may be NULL. array The array of values to pass to the public function. A single cell that must be passed by-reference is regarded as a single-cell array. numcells The number of elements in the array. Notes: Any parameters to a public function must be pushed to the func- tion before calling amx_Exec. If a public function has multiple ar- guments, the arguments must be pushed int reverse order. The function allocates memory for the array inside the “heap” of the abstract machine. This memory must be freed with amx_Release. See function amx_Allot for details on the parameters amx_addr and phys_addr. See also: amx_Exec, amx_Push, amx_PushString, amx_Release amx_PushString - Pass a string argument Syntax: Code: amx PushString(AMX *amx, cell *amx_addr, cell **phys_addr, *string, pack, use_wchar); amx The abstract machine. amx_addr The address of the allocated cell as the pawn program (that runs in the abstract machine) can access it, needed to release the memory block. This parameter may be NULL. phys_addr The address of the cell for C/C++ programs to access.This parameter may be NULL. string The string to pass to the public function. pack Non-zero to convert the source string to a packed string in the abstract machine, zero to convert the source string to a cell string. use_wchar A non-zero value interprets the string argument as a pointer to “wide characters” i.e. wchar_t, regardless of its char type. This allows the function to accept Unicode strings. Notes: Any parameters to a public function must be pushed to the func- tion before calling amx_Exec. If a public function has multiple ar- guments, the arguments must be pushed int reverse order. The function allocates memory for the array inside the “heap” of the abstract machine. This memory must be freed with amx_Release. See function amx_Allot for details on the parameters amx_addr and phys_addr. When you pass in an Unicode string and request a packed format in the abstract machine (i.e. both pack and use_wchar are true), the characters are truncated to 8-bits. See also: amx_Exec, amx_Push, amx_PushArray, amx_Release, amx_SetString amx_Register - Make native functions known Syntax: Code: amx Register(AMX *amx, AMX NATIVE INFO *list, number); amx The abstract machine. list An array with structures where each structure holdsa pointer to the name of a native function and a function pointer. The list is optionally terminated with astructure holding two NULL pointers. number The number of structures in the list array, or -1 if thelist ends with a structure holding two NULL pointers. Notes: On success, this function returns 0 (AMX_ERR_NONE). If this func- tion returns the error code AMX_ERR_NOTFOUND, one or more native functions that are used by the pawn program are not found in the provided list. You can call amx_Register again to register addi- tional function lists. To check whether all native functions used in the compiled script have been registered, call amx_Register with the parameter list set to NULL. This call will not register any new native functions, but still return AMX_ERR_NOTFOUND if any native function is unregistered. See also: amx_NativeInfo amx_Release - Free heap space in the abstract machine Syntax: Code: amx Release(AMX *amx,cell amx_addr); amx The abstract machine. amx_addr The address of the allocated cell as the pawn program (that runs in the abstract machine) sees it. This value is returned by amx_Allot, amx_PushArray and amx_PushString. Notes: amx_Allot allocates memory on the heap in ascending order (the heap grows upwards). amx_Release frees all memory above the value of the input parameter amx_addr. That is, a single call to amx_Release can free multiple calls to amx_Allot if you pass the amx_addr value of the first allocation. amx_PushArray and amx_PushString use amx_Allot internally, so the same procedure applies to these functions as well. See also: amx_Allot, amx_PushArray, amx_PushString amx_SetString - Store a string in the abstract machine Syntax: Code: amx SetString(cell *dest, *source, pack, use_wchar, size_t size); dest A pointer to a character array in the amx where the converted string is stored. Use amx_GetAddr to convert a string address in the amx to the physical address. source A pointer to the source string. pack Non-zero to convert the source string to a packed string in the abstract machine, zero to convert the source string to a cell string. use_wchar A non-zero value interprets the source argument as a pointer to “wide characters” —i.e. wchar_t, regardless of its char type. This allows the function to accept Unicode strings. size The maximum number of cells to store in dest, including the terminating zero byte or cell. If the string in the source is longer than can fit in the number of cells in dest, it will be truncated. Notes: When you pass in an Unicode string and request a packed format in the abstract machine (i.e. both pack and use_wchar are true), the characters are truncated to 8-bits. See also: amx_GetString amx_StrLen - Get the string length in characters Syntax: Code: amx_StrLen( cell *cstring, *length); cstring The string in the abstract machine. length This parameter will hold the string length upon return. Notes: This function determines the length in characters of the string, not including the zero-terminating character (or cell). A packed string occupies less cells than its number if characters. If the cstring parameter is NULL, the length parameter is set to zero (0) and the function returns with an error code. For converting unpacked strings to UTF-8, function amx_UTF8Len may be more convenient. See also: amx_GetAddr, amx_GetString, amx_SetString, amx_StrParam, amx_UTF8Len amx_StrParam - Get a string parameter from an abstract machine Syntax: Code: amx_StrParam([AMX*] amx, ] param, [*] result); amx The abstract machine. param The parameter number. result A variable that will hold the result on return. Notes: This macro allocates a block of memory (with alloca) and copies a string parameter (to a native function) in that block. See page 56 for an example of using this macro. See also: amx_GetAddr, amx_GetString, amx_StrLen Amx function examples In an attempt at avoiding huge text walls, I've decided to add comments to the code explaining what I'm doing instead of typing it out in the topic. It's probably a good idea to have the wiki article open as a reference while reading the examples. Registering natives Every single function we provide PAWN with has to be registered so the abstract machine can be aware of its existence. So i think its probably a good idea to start off with amx_Regester! Luckily we actually already used an example of registering a native one of our previous sections, so I'll be using that example again and adding some comments. pawn Code: //An array of the functions we wish to register with the abstract machine. AMX_NATIVE_INFO PluginNatives[] = { //Here we specify our native functions information and terminate the array with two null values. {"HelloWorld", HelloWorld}, {0, 0} }; PLUGIN_EXPORT int PLUGIN_CALL AmxLoad( AMX *amx ) { //Here we register our natives to the abstract machine. Note how we're using -1. Normally this would have to be the number of //functions we're registering, but since we terminated the array with two null values, we can specify -1. return amx_Register(amx, PluginNatives, -1); } Getting a string and its length pawn Code: //This function demonstrates: how to get a string (and its length) from PAWN. //PAWN native: native PrintPawnString(const str[]); cell AMX_NATIVE_CALL PrintPawnString(AMX* amx, cell* params) { int len = NULL, ret = NULL; cell *addr = NULL; //Get the address of our string param (str) and then get its length amx_GetAddr(amx, params[1], &addr); amx_StrLen(addr, &len); //if the length of input isnt 0 if(len) { //We increase len because we want to make room for the terminating null char when we allocate memory. //Also because GetString's size parameter counts the null chracter, we have to specify the length //of the string + 1; otherwise our string will be truncated to make room for the null char (we'd lose 1 character). len++; //Allocate memory to hold our string we're passing (str) and then "get" the string using our allocated memory to store it. char* text = new char[ len ]; amx_GetString(text, addr, 0, len); //Use logprintf to print out string (text). We dont use std::cout because it doesnt write to the server log (only the console). logprintf(text); //Deallocate our memory... delete[] text; } return 1; } Alternative way to get a string I decided to add this section because i know people are going to be tempted by the simplicity of it, and i think they should know the risks and problems that can come from it. pawn Code: //This function demonstrates: an alternative method to getting strings from pawn- and the possible risks that come with it. //PAWN native: native PrintPawnString2(const str[]); cell AMX_NATIVE_CALL PrintPawnString2(AMX* amx, cell* params) { //This method is NOT recomended as the amx_StrParam macro uses the alloca function which is NOT a standard in C, OR C++. //Using this method comes with risks of overflowing the stack (If you allocate large amounts of memory) and also //gives you the risk of bugs (this function is machine AND compiler dependent- some implementations are said to be bugged). char* text = NULL; amx_StrParam(amx, params[1], text); //Check if text is null if(text != NULL) { //Use logprintf to print out string (text). We dont use std::cout because it doesnt write to the server log (only the console). logprintf(text); } return 1; } How to set a string pawn Code: //This function demonstrates: how to modify a PAWN string. //PAWN native: native SetPawnString(str[], len = sizeof(str)); cell AMX_NATIVE_CALL SetPawnString(AMX* amx, cell* params) { const string message = "This is a string from C/++!!"; cell* addr = NULL; //Get the address of our string parameter (str) and store our message amx_GetAddr(amx, params[1], &addr); amx_SetString(addr, message.c_str(), 0, 0, params[2]); return 1; } Casting and returning floats pawn Code: //This function demonstrates: how to cast a float to a PAWN float, and return it. //PAWN native: native Float:ReturnPawnFloatVal(); cell AMX_NATIVE_CALL ReturnPawnFloatVal(AMX* amx, cell* params) { //Since PAWN is a typeless language it stores everything as a 32bit integer and relies on tags to handle special data. //A floating point number is no exception to this; It's a still 32bit int, but it has a Float tag to show that it shouldnt //be treated like an regular integer. So how do we convert a float to an 32bit integer (for PAWN) without losing data? //The answer is the amx_ftoc macro! //The macro amx_ftoc type casts a float into a cell while preserving its bit pattern (amx_ctof does the inverse). const float f = 22.624f; return amx_ftoc(f); } Passing parameters by reference pawn Code: //This function demonstrates: How to pass parameters by reference. //PAWN native: native SetPawnReferenceVars(&value1, &Float:value2); cell AMX_NATIVE_CALL SetPawnReferenceVars(AMX* amx, cell* params) { const int val = 65; const float val2 = 84.54f; cell* addr[2] = {NULL, NULL}; //Get the addresses of "value1" and "value2" amx_GetAddr(amx, params[1], &addr[0]); amx_GetAddr(amx, params[2], &addr[1]); //Dereference our pointers and assign our values. Remember to ALWAYS use the macro "amx_ftoc" to convert floats into //cells (the appropriate float format for PAWN)! *addr[0] = val; *addr[1] = amx_ftoc(val2); return 1; } Getting and modding array values pawn Code: //This function demonstrates: how to get and modify array values. //PAWN native: native PrintPawnArray(arr[], size = sizeof(arr)); cell AMX_NATIVE_CALL PrintPawnArray(AMX* amx, cell* params) { //Make sure there's something to print... if(params[2] > 0) { cell* addr = NULL; //Get the address of the first value in our PAWN array. amx_GetAddr(amx, params[1], &addr); for(int i = 0, l = params[2]; i < l; i++) { //This is pretty straight forward: We dereference the addr pointer to get our value to print. //You should know this already, but arrays and pointers are almost the same thing, so we can use pointer //arithmetic to add an offset OR just use the subscript operator (in the end *(addr+1) and addr[1] are the same). logprintf("arr[%d] = %d", i, *(addr + i)); //If you wanted to modify the array you would just change its value by dereferencing addr and assigning a new value. //You should know this as well, im just adding it in for completeness. Here we change the first value of our array //to 5 (Note: Since its the first value, no offset it used). // *(addr) = 5; } } return 1; } Call a callback pawn Code: //This function demonstrates: How to call a callback that is in a PAWN script. //PAWN native: native EmitPawnCallback(); cell AMX_NATIVE_CALL EmitPawnCallback(AMX* amx, cell* params) { int idx; const cell var = 3; const cell arr[] = {100, 4, 33}; const string str = "Some random message from C++."; //Pawn callback: forward OnPawnCallbackEmitted(var, arr[], str[]); //Find our callback and store its place in the public function table (it's index) into our idx var. if(!amx_FindPublic(amx, "OnPawnCallbackEmitted", &idx)) { cell ret, addr; //Here we push our arguments to our function. Note that if the function has multiple arguments you have to push your //values in reverse order! Thats why we're pushing the string first, then the array, and finally our integer. amx_PushString(amx, &addr, NULL, str.c_str(), NULL, NULL); //amx_PushArray(amx, NULL, NULL, arr, sizeof(arr) / sizeof(cell)); cell amx_addr, *phys_addr; //For some reason amx_PushArray seems to be crashing the server, and i have NO idea why. My usage should be completely //valid judging from the implementers guide, and the code itself. Since the function isnt working we'll have to //use the old method and allocate the memory, set it, and push it all ourselves. This is pretty straight forward. We //allocate memory on the heap using amx_Allot (this returns 2 addresses- one of the location in the abstract machine //(amx_addr), and one relative to the actual server's address space (phsy_addr - which we can use in C++)). Once the //memory is allocated we use memcpy to copy the memory from our array to our phys_addr address location. amx_Allot(amx, sizeof(arr) / sizeof(cell), &amx_addr, &phys_addr); memcpy(phys_addr, arr, sizeof(arr)); amx_Push(amx, amx_addr); //Push our integer value amx_Push(amx, var); //Execute our function using our previously obtained idx var. //Note: This function's second parameter is what the callback returned (Can be NULL if you're not interested in return values). amx_Exec(amx, &ret, idx); //Release our memory that we allocated. The function amx_Alloc allocates memory on the heap in the abstract machine. //The functions amx_PushString and amx_PushArray both use this function internally so you have to release the memory every time //you use one of those functions. NOTE: We used both amx_PushString and amx_PushArray, and yet we only have ONE release call. //This is because memory on the heap is allocated in ascending order! amx_Release release all the memory above a certain point //(the second parameter, amx_addr - which is our addr variable). Since it does this we ONLY store the address from the amx_PushString //call, as it'll delete everything from that point on. amx_Release(amx, addr); //Print the return value (for completeness). logprintf("EmitPawnCallback NOTE: OnPawnCallbackEmitted callback returned %d!", ret); } return 1; } Calling natives and hooking callbacks Before i get into callback hooking or SA-MP native calling, i thought i should shed light on something first. Some people seem to think that plugins are a replacement for PAWN, this simply isnt the case. Plugins were designed to provide functionality TO PAWN, not to serve as a replacement for it. There are times when developing something as a plugin rather then a PAWN script doesnt make sense- its important to ask yourself if you should really be coding something as a plugin rather then a pawn script. There are a lot of different methods you can use for both callback hooking and sa-mp native calling. Im only going to be focusing on one method oh each for now though. Invoke Invoke is a SA-MP native function calling method that was created by incognito. You can find it here. Please note that there is a text file in there containing a function with tons of SA-MP native function calls. This is vital for invoke, and must be added to your plugin's include file. Invoke will not work if you dont have a public PAWN function that uses all the SA-MP natives your plugin requires. This function is only so invoke can find the addresses of the natives, and should never actually be used anywhere. Getting started Before we can start calling SA-MP natives, we have to setup Invoke. The first thing we have to do is include it inside of our project, you should remember how to do this from the previous "Getting started" section. Once you have the files added to your project there are just a few simple steps to get Invoke working: Include the invoke header. Allocate memory for the invoke instance under Load. Push back the amx_list member variable every time a new AMX instance is loaded under AmxLoad. Every time a script gets unloaded, iterate through Invoke's amx_list, find the unloaded AMX instance, and erase it under AmxUnload. Create a native that calls Invoke's "getAddresses" member function for PAWN to use when a filterscript or gamemode is loaded that uses our plugin. Thats about it for getting invoke setup for your plugin. The only thing left to do is to add the public PAWN function found in the invoke package (the txt file we spoke of earlier) to your plugin's include, and use the native from the last setup step when a script is loaded. Anyone who will be creating plugins for public release, i encourage you to look into THIS post. It allows for a clean seamless "hook" of the OnGameModeInit/OnFilterScriptInit callbacks so you can register SA-MP's native addresses while not forcing the end user to add a function into those callbacks themselves. Note: Im aware that explaining this in text form probably isnt ideal for most newer users; however these are all standard C/++ tasks that you should already be familiar with- and those that arent (ie: creating a native) were already explained! If you're having trouble following along, i suggest getting a bit more familiar with C/++. With that being said though there will be an updated source code file (along with an actual include for our plugin) later on in this article with comments on everything we've learned so far. Look at that updated source file if you have any problems. Adding SA-MP natives Sadly invoke isnt magic, and does require maintenance to add new natives or remove deprecated ones. The version from the previous section has all the natives from the 0.2.2 version of SA-MP, so its missing all the newer functions. For these two reasons i decided to add this section! If someone is nice enough to add all the missing natives i will update the invoke package and include them in the credits- if not, i suggest adding natives on an as needed basis. Inside of the invoke header you should see a big array of function names (the array is appropriately called "name") and a lot of static "Native" structure declarations. To add a native you simply have to add its name to the array of names, and declare a Native structure with the appropriate information- name, and parameter specifiers. There are a lot of available parameter specifiers available to use: Code: i = integer f = float value s = string v = reference variable (GetPlayerPos, GetPlayerKeys, etc.) p = string var (GetPlayerName etc.) Remember to update the public PAWN function that calls all the natives. Any time you add a native you need to also add it to said public function. Calling natives Invoke uses the variadic member function appropriately named "callNative" to call natives. The parameters for this function are a pointer to a Native structure (see previous section) containing info on the native function, and an ellipsis for all the parameters for said native function. The function returns the return value of the native that is being called. Here is an example function that calls multiple natives, with differing parameter types (int, reference, and string var). pawn Code: //This function demonstrates: how to use invoke to call SA-MP natives. //PAWN native: native WhereIsPlayer(playerid); cell AMX_NATIVE_CALL WhereIsPlayer(AMX* amx, cell* params) { float x = NULL, y = NULL, z = NULL; //Get the player's position (and check to see if he is even connected). if(g_Invoke->callNative(&PAWN::GetPlayerPos, params[1], &x, &y, &z)) { char name[24]; //Get the rest of the player's information (name, interior, and virtualworld) and print it. g_Invoke->callNative(&PAWN::GetPlayerName, params[1], name); int interior = g_Invoke->callNative(&PAWN::GetPlayerInterior, params[1]); int virtualworld = g_Invoke->callNative(&PAWN::GetPlayerVirtualWorld, params[1]); logprintf("%s is at X: %.2f, Y: %.2f, Z: %.2f (Virtual world: %d, Interior %d).", name, x, y, z, virtualworld, interior); return 1; } return 0; } Hooking callbacks Using the invoke method, there is no 100% transparent hooking method when it comes to callbacks. Plugins that want to hook callbacks that are using invoke have go through PAWN to do so. You can create a native function that you can call inside the PAWN callback you wish to hook, and use that native as a sort of pseudo callback. You can do this in a manner that requires no work on the end user by using the ALS hooking method. The first step is obviously creating the native (remember to add it to our list of natives to register, and also add it in your include later on). The callback i decided to hook is OnPlayerConnect, so im gonna write a native function that has the same arguments as the callback, and then add the code that requires the callback (in this case a little joke). pawn Code: //This function demonstrates: how to write a native to act as a callback. //PAWN native: native TEST_Hook_OnPlayerConnnect(playerid); cell AMX_NATIVE_CALL TEST_Hook_OnPlayerConnnect(AMX* amx, cell* params) { //Get the players name char name[24]; g_Invoke->callNative(&PAWN::GetPlayerName, params[1], name); //Check if his name is "Mario". if(string("Mario") == name) { //If it is send our funny little message and kick him. g_Invoke->callNative(&PAWN::SendClientMessage, params[1], 0xFFFFFFFF, "Sorry {FF0000}Mario, {FFFFFF}but your princess is in another server."); g_Invoke->callNative(&PAWN::Kick, params[1]); } return 1; } TEST_Hook_OnPlayerConnnect will now act as our plugin's OnPlayerConnect callback. Since our plugin's name is "Test" and its rather short i decided to let that act as our prefix for hooking. You generally want something unique for your prefixes so you dont collide with other people's plugins/includes. Note: We still have to call this function from PAWN using the ALS method we spoke of earlier for user convenience. Since Y_Less does a great job of explaining the ALS method, there's really no point for me to go into details about it, i will simply provide hooking example for our TEST_Hook_OnPlayerConnect callback. This should be put in your plugins include file. pawn Code: public OnPlayerConnect(playerid) { TEST_Hook_OnPlayerConnnect(playerid); //.. if(funcidx("TEST_OnPlayerConnect") != -1) { return CallLocalFunction("TEST_OnPlayerConnect", "d", playerid); } return 1; } #if defined _ALS_OnPlayerConnect #undef OnPlayerConnect #else #define _ALS_OnPlayerConnect #endif #define OnPlayerConnect TEST_OnPlayerConnect forward TEST_OnPlayerConnect(playerid); Closing Some of you experienced plugin developers might be wondering why i didnt include the GDK in the "Calling natives and hooking callbacks" section. I decided to leave it out for two reasons. The first being that i simply do not have that much available time anymore, and couldnt spare any getting used to this new system. The second reason is that the GDK is very experimental and currently is struggling with crashes amongst other problems. Im quite aware of the speed differences between Invoke and GDK, and definitely will add it some time in the future if no one else does (remember, this is suppose to be a community driven thing). I think its best to wait for some more of the issues to be ironed out for now though (zeex is an amazing coder, and im sure he'll iron everything out ). Also, as i promised, here is the update include/source file with comments on everything we've hopefully learned: Plugin include: pawn Code: //Anything with the prefix "TEST" should be changed if you called your plugin something //different. This also goes for the plugin code (TEST_Hook_OnPlayerConnect specifically). #if defined _TEST_INCLUDED #endinput #endif #define _TEST_INCLUDED #pragma library Test #include <a_samp> //Example natives native PrintPawnString(const str[]); native PrintPawnString2(const str[]); native SetPawnString(str[], len = sizeof(str)); native Float:ReturnPawnFloatVal(); native SetPawnReferenceVars(&value1, &Float:value2); native PrintPawnArray(arr[], size = sizeof(arr)); native EmitPawnCallback(); native WhereIsPlayer(playerid); native TEST_Hook_OnPlayerConnnect(playerid); //Invoke native native Invoke_GetAddresses(); //Callback hooks public OnPlayerConnect(playerid) { TEST_Hook_OnPlayerConnnect(playerid); //.. if(funcidx("TEST_OnPlayerConnect") != -1) { return CallLocalFunction("TEST_OnPlayerConnect", "d", playerid); } return 1; } #if defined _ALS_OnPlayerConnect #undef OnPlayerConnect #else #define _ALS_OnPlayerConnect #endif #define OnPlayerConnect TEST_OnPlayerConnect forward TEST_OnPlayerConnect(playerid); public OnGameModeInit() { Invoke_GetAddresses(); //.. if(funcidx("TEST_OnGameModeInit") != -1) { return CallLocalFunction("TEST_OnGameModeInit", ""); } return 1; } #if defined _ALS_OnGameModeInit #undef OnGameModeInit #else #define _ALS_OnGameModeInit #endif #define OnGameModeInit TEST_OnGameModeInit forward TEST_OnGameModeInit(); public OnFilterScriptInit() { Invoke_GetAddresses(); //.. if(funcidx("TEST_OnFilterScriptInit") != -1) { return CallLocalFunction("TEST_OnFilterScriptInit", ""); } return 1; } #if defined _ALS_OnFilterScriptInit #undef OnFilterScriptInit #else #define _ALS_OnFilterScriptInit #endif #define OnFilterScriptInit TEST_OnFilterScriptInit forward TEST_OnFilterScriptInit(); //Public function for invoke forward InvokeFunction(); public InvokeFunction() { new Float:fVar; new Var[ 256 ]; new iVar; // a_samp.inc SendClientMessage(0, 0, ""); SendClientMessageToAll(0, ""); SendDeathMessage(0, 0, 0); GameTextForAll("", 0, 0); GameTextForPlayer(0, "", 0, 0); GetTickCount(); GetMaxPlayers(); SetGameModeText(""); SetTeamCount(0); AddPlayerClass(0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0); AddPlayerClassEx(0, 0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0); AddStaticVehicle(0, 0.0, 0.0, 0.0, 0.0, 0, 0); AddStaticVehicleEx(0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0); AddStaticPickup(0, 0, 0.0, 0.0, 0.0); ShowNameTags(0); ShowPlayerMarkers(0); GameModeExit(); SetWorldTime(0); GetWeaponName(0, Var, sizeof( Var ) ); EnableTirePopping(0); AllowInteriorWeapons(0); SetWeather(0); SetGravity(0.0); AllowAdminTeleport(0); SetDeathDropAmount(0); CreateExplosion(0.0, 0.0, 0.0, 0, 0.0); //SetDisabledWeapons(); EnableZoneNames(0); IsPlayerAdmin(0); Kick(0); Ban(0); SendRconCommand(""); ShowPlayerDialog(0,0,0,"lol","lol","lol","lol"); // a_players.inc SetSpawnInfo(0, 0, 0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0,0); SpawnPlayer(0); SetPlayerPos(0, 0.0, 0.0, 0.0); // SetPlayerPosFindZ(0, 0.0, 0.0, 0.0); GetPlayerPos(0, fVar, fVar, fVar); SetPlayerFacingAngle(0,0.0); GetPlayerFacingAngle(0,fVar); SetPlayerInterior(0,0); GetPlayerInterior(0); SetPlayerHealth(0, 0.0); GetPlayerHealth(0, fVar); SetPlayerArmour(0, 0.0); GetPlayerArmour(0, fVar); SetPlayerAmmo(0, 0,0); GetPlayerAmmo(0); SetPlayerTeam(0,0); GetPlayerTeam(0); SetPlayerScore(0,0); GetPlayerScore(0); SetPlayerColor(0,0); GetPlayerColor(0); SetPlayerSkin(0,0); GivePlayerWeapon(0, 0,0); ResetPlayerWeapons(0); GetPlayerWeaponData(0, 0, iVar, iVar ); GivePlayerMoney(0,0); ResetPlayerMoney(0); SetPlayerName(0, ""); GetPlayerMoney(0); GetPlayerState(0); GetPlayerIp(0, Var, sizeof( Var )); GetPlayerPing(0); GetPlayerWeapon(0); GetPlayerKeys(0,iVar,iVar,iVar); GetPlayerName(0, Var, sizeof( Var )); PutPlayerInVehicle(0, 0,0); GetPlayerVehicleID(0); RemovePlayerFromVehicle(0); TogglePlayerControllable(0,0); PlayerPlaySound(0, 0, 0.0, 0.0,0.0); SetPlayerCheckpoint(0, 0.0, 0.0, 0.0,0.0); DisablePlayerCheckpoint(0); SetPlayerRaceCheckpoint(0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,0.0); DisablePlayerRaceCheckpoint(0); SetPlayerWorldBounds(0,0.0,0.0,0.0,0.0); SetPlayerMarkerForPlayer(0, 0,0); ShowPlayerNameTagForPlayer(0, 0,0); SetPlayerMapIcon(0, 0, 0.0, 0.0, 0.0, 0,0); RemovePlayerMapIcon(0,0); SetPlayerCameraPos(0,0.0, 0.0, 0.0); SetPlayerCameraLookAt(0, 0.0, 0.0, 0.0); SetCameraBehindPlayer(0); AllowPlayerTeleport(0,0); IsPlayerConnected(0); IsPlayerInVehicle(0,0); IsPlayerInAnyVehicle(0); IsPlayerInCheckpoint(0); IsPlayerInRaceCheckpoint(0); SetPlayerTime(0, 0,0); TogglePlayerClock(0,0); SetPlayerWeather(0,0); GetPlayerTime(0,iVar,iVar); SetPlayerVirtualWorld(0,0); GetPlayerVirtualWorld(0); // a_vehicle.inc CreateVehicle(0,0.0,0.0,0.0,0.0,0,0,0); DestroyVehicle(0); GetVehiclePos(0,fVar,fVar,fVar); SetVehiclePos(0,0.0,0.0,0.0); GetVehicleZAngle(0,fVar); SetVehicleZAngle(0,0.0); SetVehicleParamsForPlayer(0,0,0,0); SetVehicleToRespawn(0); LinkVehicleToInterior(0,0); AddVehicleComponent(0,0); ChangeVehicleColor(0,0,0); ChangeVehiclePaintjob(0,0); SetVehicleHealth(0,0.0); GetVehicleHealth(0,fVar); AttachTrailerToVehicle(0, 0); DetachTrailerFromVehicle(0); IsTrailerAttachedToVehicle(0); GetVehicleModel(0); SetVehicleNumberPlate(0,""); SetVehicleVirtualWorld(0,0); GetVehicleVirtualWorld(0); ApplyAnimation(0,"","",1.0,0,0,0,0,0); // a_objects.inc CreateObject(0,0.0,0.0,0.0,0.0,0.0,0.0); SetObjectPos(0,0.0,0.0,0.0); GetObjectPos(0,fVar,fVar,fVar); SetObjectRot(0,0.0,0.0,0.0); GetObjectRot(0,fVar,fVar,fVar); IsValidObject(0); DestroyObject(0); MoveObject(0,0.0,0.0,0.0,0.0); StopObject(0); CreatePlayerObject(0,0,0.0,0.0,0.0,0.0,0.0,0.0); SetPlayerObjectPos(0,0,0.0,0.0,0.0); GetPlayerObjectPos(0,0,fVar,fVar,fVar); GetPlayerObjectRot(0,0,fVar,fVar,fVar); SetPlayerObjectRot(0,0,0.0,0.0,0.0); IsValidPlayerObject(0,0); DestroyPlayerObject(0,0); MovePlayerObject(0,0,0.0,0.0,0.0,0.0); StopPlayerObject(0,0); // Menu's CreateMenu("", 0, 0.0, 0.0, 0.0, 0.0); DestroyMenu(Menu:0); AddMenuItem(Menu:0, 0, ""); SetMenuColumnHeader(Menu:0, 0, ""); ShowMenuForPlayer(Menu:0, 0); HideMenuForPlayer(Menu:0, 0); IsValidMenu(Menu:0); DisableMenu(Menu:0); DisableMenuRow(Menu:0,0); // Textdraw TextDrawCreate(0.0,0.0,""); TextDrawDestroy(Text:0); TextDrawLetterSize(Text:0, 0.0,0.0); TextDrawTextSize(Text:0, 0.0,0.0); TextDrawAlignment(Text:0, 0); TextDrawColor(Text:0,0); TextDrawUseBox(Text:0, 0); TextDrawBoxColor(Text:0, 0); TextDrawSetShadow(Text:0, 0); TextDrawSetOutline(Text:0, 0); TextDrawBackgroundColor(Text:0,0); TextDrawFont(Text:0, 0); TextDrawSetProportional(Text:0, 0); TextDrawShowForPlayer(0, Text:0); TextDrawHideForPlayer(0, Text:0); TextDrawShowForAll(Text:0); TextDrawHideForAll(Text:0); // Others funcidx(""); gettime(iVar,iVar,iVar); getdate(iVar,iVar,iVar); tickcount(iVar); return 1; } Plugin source code: pawn Code: #include "SDK\amx\amx.h" #include "SDK\plugincommon.h" #include "Invoke.h" #include <string> #include <vector> #include <cstdlib> #include <ctime> typedef void (*logprintf_t)(char* format, ...); logprintf_t logprintf; extern void *pAMXFunctions; using namespace std; //This function demonstrates: how to get a string (and its length) from PAWN. //PAWN native: native PrintPawnString(const str[]); cell AMX_NATIVE_CALL PrintPawnString(AMX* amx, cell* params) { int len = NULL, ret = NULL; cell *addr = NULL; //Get the address of our string param (str) and then get its length amx_GetAddr(amx, params[1], &addr); amx_StrLen(addr, &len); //if the length of input isnt 0 if(len) { //We increase len because we want to make room for the terminating null char when we allocate memory. //Also because GetString's size parameter counts the null chracter, we have to specify the length //of the string + 1; otherwise our string will be truncated to make room for the null char (we'd lose 1 character). len++; //Allocate memory to hold our string we're passing (str) and then "get" the string using our allocated memory to store it. char* text = new char[ len ]; amx_GetString(text, addr, 0, len); //Use logprintf to print out string (text). We dont use std::cout because it doesnt write to the server log (only the window). logprintf(text); //Deallocate our memory... delete[] text; } return 1; } //This function demonstrates: How to call a callback that is in a PAWN script. //PAWN native: native EmitPawnCallback(); cell AMX_NATIVE_CALL EmitPawnCallback(AMX* amx, cell* params) { int idx; const cell var = 3; const cell arr[] = {100, 4, 33}; const string str = "Some random message from C++."; //Pawn callback: forward OnPawnCallbackEmitted(var, arr[], str[]); //Find our callback and store its place in the public function table (it's index) into our idx var. if(!amx_FindPublic(amx, "OnPawnCallbackEmitted", &idx)) { cell ret, addr; //Here we push our arguments to our function. Note that if the function has multiple arguments you have to push your //values in reverse order! Thats why we're pushing the string first, then the array, and finally our integer. amx_PushString(amx, &addr, NULL, str.c_str(), NULL, NULL); //amx_PushArray(amx, NULL, NULL, arr, sizeof(arr) / sizeof(cell)); cell amx_addr, *phys_addr; //For some reason amx_PushArray seems to be crashing the server, and i have NO idea why. My usage should be completely //valid judging from the implementers guide, and the code itself. Since the function isnt working we'll have to //use the old method and allocate the memory, set it, and push it all ourselves. This is pretty straight forward. We //allocate memory on the heap using amx_Allot (this returns 2 addresses- one of the location in the abstract machine //(amx_addr), and one relative to the actual server's address space (phsy_addr - which we can use in C++)). Once the //memory is allocated we use memcpy to copy the memory from our array to our phys_addr address location. amx_Allot(amx, sizeof(arr) / sizeof(cell), &amx_addr, &phys_addr); memcpy(phys_addr, arr, sizeof(arr)); amx_Push(amx, amx_addr); //Push our integer value amx_Push(amx, var); //Execute our function using our previously obtained idx var. //Note: This function's second parameter is what the callback returned (Can be NULL if you're not interested in return values). amx_Exec(amx, &ret, idx); //Release our memory that we allocated. The function amx_Alloc allocates memory on the heap in the abstract machine. //The functions amx_PushString and amx_PushArray both use this function internally so you have to release the memory every time //you use one of those functions. NOTE: We used both amx_PushString and amx_PushArray, and yet we only have ONE release call. //This is because memory on the heap is allocated in ascending order! amx_Release release all the memory above a certain point //(the second parameter, amx_addr - which is our addr variable). Since it does this we ONLY store the address from the amx_PushString //call, as it'll delete everything from that point on. amx_Release(amx, addr); //Print the return value (for completeness). logprintf("EmitPawnCallback NOTE: OnPawnCallbackEmitted callback returned %d!", ret); } return 1; } //This function demonstrates: an alternative method to getting strings from pawn- and the possible risks that come with it. //PAWN native: native PrintPawnString2(const str[]); cell AMX_NATIVE_CALL PrintPawnString2(AMX* amx, cell* params) { //This method is NOT recomended as the amx_StrParam macro uses the alloca function which is NOT a standard in C, OR C++. //Using this method comes with risks of overflowing the stack (If you allocate large amounts of memory) and also //gives you the risk of bugs (this function is machine AND compiler dependent- some implementations are said to be bugged). char* text = NULL; amx_StrParam(amx, params[1], text); //Check if text is null if(text != NULL) { //Use logprintf to print out string (text). We dont use std::cout because it doesnt write to the server log (only the window). logprintf(text); } return 1; } //This function demonstrates: how to modify a PAWN string. //PAWN native: native SetPawnString(str[], len = sizeof(str)); cell AMX_NATIVE_CALL SetPawnString(AMX* amx, cell* params) { const string message = "This is a string from C/++!!"; cell* addr = NULL; //Get the address of our string parameter (str) and store our message amx_GetAddr(amx, params[1], &addr); amx_SetString(addr, message.c_str(), 0, 0, params[2]); return 1; } //This function demonstrates: how to cast a float to a PAWN float. //PAWN native: native Float:ReturnPawnFloatVal(); cell AMX_NATIVE_CALL ReturnPawnFloatVal(AMX* amx, cell* params) { //Since PAWN is a typeless language it stores everything as a 32bit integer and relies on tags to handle special data. //A floating point number is no exception to this; It's a still 32bit int, but it has a Float tag to show that it shouldnt //be treated like an regular integer. So how do we convert a float to an 32bit integer (for PAWN) without losing data? //The answer is the amx_ftoc macro! //The macro amx_ftoc type casts a float into a cell while preserving its bit pattern (amx_ctof does the inverse). const float f = 22.624f; return amx_ftoc(f); } //This function demonstrates: How to pass parameters by reference. //PAWN native: native SetPawnReferenceVars(&value1, &Float:value2); cell AMX_NATIVE_CALL SetPawnReferenceVars(AMX* amx, cell* params) { const int val = 65; const float val2 = 84.54f; cell* addr[2] = {NULL, NULL}; //Get the addresses of "value1" and "value2" amx_GetAddr(amx, params[1], &addr[0]); amx_GetAddr(amx, params[2], &addr[1]); //Dereference our pointers and assign our values. Remember to ALWAYS use the macro "amx_ftoc" to convert floats into //cells (the appropriate float format for PAWN)! *addr[0] = val; *addr[1] = amx_ftoc(val2); return 1; } //This function demonstrates: how to get and modify array values. //PAWN native: native PrintPawnArray(arr[], size = sizeof(arr)); cell AMX_NATIVE_CALL PrintPawnArray(AMX* amx, cell* params) { //Make sure there's something to print... if(params[2] > 0) { cell* addr = NULL; //Get the address of the first value in our PAWN array. amx_GetAddr(amx, params[1], &addr); for(int i = 0, l = params[2]; i < l; i++) { //This is pretty straight forward: We dereference the addr pointer to get our value to print. //You should know this already, but arrays and pointers are almost the same thing, so we can use pointer //arithmetic to add an offset OR just use the subscript operator (in the end *(addr+1) and addr[1] are the same). logprintf("arr[%d] = %d", i, *(addr + i)); //If you wanted to modify the array you would just change its value by dereferencing addr and assigning a new value. //You should know this as well, im just adding it in for completeness. Here we change the first value of our array //to 5 (Note: Since its the first value, no offset it used). // *(addr) = 5; } } return 1; } //This function demonstrates: setting up invoke (fetching the addresses of our natives). //PAWN native: native Invoke_GetAddresses(); cell AMX_NATIVE_CALL Invoke_GetAddresses(AMX* amx, cell* params) { return g_Invoke->getAddresses(); } //This function demonstrates: how to use invoke to call SA-MP natives. //PAWN native: native WhereIsPlayer(playerid); cell AMX_NATIVE_CALL WhereIsPlayer(AMX* amx, cell* params) { float x = NULL, y = NULL, z = NULL; //Get the player's position (and check to see if he is even connected). if(g_Invoke->callNative(&PAWN::GetPlayerPos, params[1], &x, &y, &z)) { char name[24]; //Get the rest of the player's information (name, interior, and virtualworld) and print it. g_Invoke->callNative(&PAWN::GetPlayerName, params[1], name); int interior = g_Invoke->callNative(&PAWN::GetPlayerInterior, params[1]); int virtualworld = g_Invoke->callNative(&PAWN::GetPlayerVirtualWorld, params[1]); logprintf("%s is at X: %.2f, Y: %.2f, Z: %.2f (Virtual world: %d, Interior %d).", name, x, y, z, virtualworld, interior); return 1; } return 0; } //This function demonstrates: how to write a native to act as a callback. //PAWN native: native TEST_Hook_OnPlayerConnnect(playerid); cell AMX_NATIVE_CALL TEST_Hook_OnPlayerConnnect(AMX* amx, cell* params) { //Get the players name char name[24]; g_Invoke->callNative(&PAWN::GetPlayerName, params[1], name); //Check if his name is "Mario". if(string("Mario") == name) { //If it is send our funny little message and kick him. g_Invoke->callNative(&PAWN::SendClientMessage, params[1], 0xFFFFFFFF, "Sorry {FF0000}Mario, {FFFFFF}but your princess is in another server."); g_Invoke->callNative(&PAWN::Kick, params[1]); } return 1; } //This function tells the server what capabilities our plugin will have based on what it returns. Generally we only use 3 support flags //in plugins: SUPPORTS_VERSION, SUPPORTS_AMX_NATIVES, and SUPPORTS_PROCESS_TICK. PLUGIN_EXPORT unsigned int PLUGIN_CALL Supports() { //Note: If you're using the ProccessTick function, remember to export it in the .def file! return SUPPORTS_VERSION | SUPPORTS_AMX_NATIVES | SUPPORTS_PROCESS_TICK; } //The Load function is pretty straight forward. This is called when the plugin is loaded and gets passed an array of addresses that the plugin //will use to function. The two indexes we typically use are PLUGIN_DATA_AMX_EXPORTS, and PLUGIN_DATA_LOGPRINTF. PLUGIN_EXPORT bool PLUGIN_CALL Load(void **ppData) { //allocate memory for out g_Invoke instance g_Invoke = new Invoke; //Assign the addresses of our AMX function table/logprintf function to their corasponding pointers. pAMXFunctions = ppData[PLUGIN_DATA_AMX_EXPORTS]; logprintf = (logprintf_t) ppData[PLUGIN_DATA_LOGPRINTF]; logprintf("* Test plugin was loaded."); return true; } //Unload is called when the plugin is unloaded (server shutdown). PLUGIN_EXPORT void PLUGIN_CALL Unload() { logprintf("* Test plugin was unloaded."); } //Our array of native info for amx_Register (function name and address). AMX_NATIVE_INFO PluginNatives[] = { {"PrintPawnString", PrintPawnString}, {"PrintPawnString2", PrintPawnString2}, {"SetPawnString", SetPawnString}, {"ReturnPawnFloatVal", ReturnPawnFloatVal}, {"SetPawnReferenceVars", SetPawnReferenceVars}, {"PrintPawnArray", PrintPawnArray}, {"EmitPawnCallback", EmitPawnCallback}, {"WhereIsPlayer", WhereIsPlayer}, {"Invoke_GetAddresses", Invoke_GetAddresses}, {"TEST_Hook_OnPlayerConnnect", TEST_Hook_OnPlayerConnnect}, {0, 0} }; //This function is called when a new AMX instance is loaded into the server. This will be called for every filterscript/gamemode! Because of this it isnt //a good idea to store a single AMX instance for the entire plugin, instead use a queue/list/vector. In this function we also register our custom //native functions we wish to provide PAWN with. PLUGIN_EXPORT int PLUGIN_CALL AmxLoad( AMX *amx ) { //Any time a script is loaded we want to add it to invoke's AMX list, so we push back the list with the instance. g_Invoke->amx_list.push_back(amx); return amx_Register(amx, PluginNatives, -1); } //This function is called when every an AMX instance is unloaded. If you store AMX instances, make sure you remove them. Otherwise you'll have instances to //non-existing gamemodes/filterscripts. PLUGIN_EXPORT int PLUGIN_CALL AmxUnload( AMX *amx ) { //Every script that is unloaded needs to get removed from our AMX instance list. So we iterate through our list and find the instance that is being //unloaded, and we earase it from the list. for(list<AMX *>::iterator i = g_Invoke->amx_list.begin(); i != g_Invoke->amx_list.end(); ++i) { if(*i == amx) { g_Invoke->amx_list.erase(i); break; } } return AMX_ERR_NONE; } //ProcessTick is a function that gets called on every iteration of the server's loop. People generally use this function as a method of managing time by //keeping track of the amount of ticks that have passed. The SA-MP server is said to have a sleep time of 5ms, so if 50 ticks go by you have an idea of // the elapsed time (5 * 50 = 250ms). Note: Anyone who uses threads in their plugins and require PAWN interaction needs to use this function to ensure //PAWN isnt busy doing another task! PLUGIN_EXPORT void PLUGIN_CALL ProcessTick() { static int tick = 0; const int NYAN_COUNT = 100; tick++; //Use the modulo operator to see if NYAN_COUNT ticks have passed (100 tick. 5ms sleep time * 100 ticks = 500ms). if(!(tick % NYAN_COUNT)) { //If 100 ticks have passed send our funny little message to everyone. const char nyan[] = "{FF0000}NYAN {FFA500}NYAN {FFFF00}NYAN {00FF00}NYAN {0000FF}NYAN {551A8B}NYAN"; g_Invoke->callNative(&PAWN::SendClientMessageToAll, 0, nyan); } } Special thanks RyDeR` - For contributing the pictures from his plugin tutorial. Incognito - For Invoke, information about ProcessTick, and for all the help over my time learning C++. Zeex/0x5A656578 - For GDK (will be included some time), and for volunteering to answer questions about GDK. Y_Less - For being generally awesome and constantly helping me when ever im completely baffled. Techboy123 - For a ton of productive chats, help, and for listening to my complaints .
  9. Lots of people have trouble while they are trying to use the downgrader patch. Some of them are just lazy,and some others don't have the basic knowledge of the computers. So,i decided to make a step by step tutorial,<How to downgrade you copy of GTA:San Andreas>. Step 1: First of all you have to download the patcher: Download Step 2: Now the download will be a zip file. The real patch is in that archive. Right click on gtasapatch.zip abd click on Extract all Click Next 3 times and you've extracted the file This is how the file shold look like: Step 3: Double click gtasapatch.exe and this window should pop-up: Click Next Click Next Now the patcher needs to know your GTA:San Andreas installation path Like "C:\Program Files\:Rockstar Games\GTA San Andreas" Attention:Giving and incorrect path will result in a failure! You can also click on "..." button to browse for GTA:SA Click Next Now the patcher will check for your GTA:SA version and it will patch it. Now your GTA:SA is at version 1.0 Enjoy,and don't hesistate to ask if you got stuck somewhere! Cheers! Attention Having a cracked version of San Andreas wil also result in a failure. The crack modifies somethings in the executable and so it will make it an unknown version! Also the Steam and Direct2Drive version are not supported! The supported version are USA 1.01 USA 2.00 EU 1.01 EU 2.00 GER 1.01 GER 1.00
  10. An optional client update SA-MP 0.3.7-R2 is now available on the Download Page. The 0.3.7-R2 client addresses a problem with the server browser where it was vulnerable to an IP spoofing attack. Servers could send fake information coming from other servers if they could guess when the client was querying them. Notes for server owners: The SA-MP query protocol is changed. If you are filtering the 'Port' section of the query packet with a firewall, you should remove this filter. Changes to PlayerPlaySound. There have been some changes to the way client's handle PlayerPlaySound. Sound ID 1 can be used disable the interior 0 ambience track (wind noise) to create fake interiors. The game's ambience system is used for all sounds below ID 1000, which means many ambience tracks that previously didn't work should now work. Sound ID 0 can be used as normal to stop the current track or return the game's normal outdoor ambience. Please check back for future updates.
  11. This update is for SA-MP server owners only Update 0.3.7 R2-2: - Changes the query flood control to deal with different query types independently. - Connection cookie logging is disabled by default. Downloads: Please review the SA-MP Services Agreement if you wish to run a public SA-MP server. SA-MP 0.3.7 R2-2 Linux Server: https://files.sa-mp.club/samp037svr_R2-2-1.tar.gz SA-MP 0.3.7 R2-2 Windows Server: https://files.sa-mp.club/samp037_svr_R2-2-1_win32.zip
  12. This update is for SA-MP server owners only - Adds protection against hackers who spam OnPlayerConnect messages. - Weapon ID checking after OnPlayerWeaponShot in case the script doesn't filter it. - Actors now default to being invulnerable (this might be needed later for a bandaid rotation fix in the client). - Adds console variable 'conncookies' (int) to disable the 0.3.7 connection cookie system. - Adds console variable 'cookielogging' to disable logging of connection cookies requested by newly connecting players. - Adds SHA256_PassHash() function for hashing player passwords. SQLite stuff: - The sqlite functions db_open and db_query now return indexes (starting at 1) instead of memory handles. That means the server tracks the memory handles internally and won't crash if invalid handles are used. - Several extra sqlite db_* functions have been added. Please see the a_sampdb.inc for a full list. - Adds %q to format(). %q is the same as %s although the input will first be passed through sqlite's string escaping function. - Adds console variable 'db_logging'. Logs sqlite db_* function errors to the main server_log. - Adds console variable 'db_log_queries'. Logs all sqlite db_query() calls including the query string. Server variable system (SVars): - The SVar system is the same as the PVars, although the variables created are server-wide, not attached to any playerid, and persist through gamemode changes. The pawn functions intended to read console/config variables (GetServerVarAs*) have been deprecated due to a naming conflict with the SVar system. These functions will still work for the time being. Moving forward it's recommended you rename these functions as listed below: Code: native GetConsoleVarAsString(const varname[], buffer[], len); native GetConsoleVarAsInt(const varname[]); native GetConsoleVarAsBool(const varname[]); Update 0.3.7 R2-1: - Adds null value checks to sqlite db_get*. These functions would crash on invalid rows or field names. - Updates SHA256_PassHash() sha256 implementation to fix a padding problem. - Fixes an index problem with PVars and SVars where *GetUpperIndex would return 1 when all vars had been deleted. Downloads: SA-MP 0.3.7 R2-1 Linux Server: https://files.sa-mp.club/samp037svr_R2-1.tar.gz SA-MP 0.3.7 R2-1 Windows Server: https://files.sa-mp.club/samp037_svr_R2-1-1_win32.zip
  13. SA-MP 0.3.7 is released! You can find it on the Download page. SA-MP 0.3.7 Updates - Over 500 new object IDs added, including stunt objects and land objects. - Interface font size changing. - Some new variations of the San Andreas cop skins. - Server control of the car doors and windows. - The ability to add sirens for unmarked cop cars. - A simple static actor system to more easily create actor NPCs for shops. - Many bug fixes and new scripting features. Special thanks to Matite and GamerX server for contributing the new the object IDs. Thanks to everyone who tested the new version while it was in testing. We hope you enjoy the new version. Please check back for any updates.
×
×
  • Create New...