s1lentq / reapi

AMX Mod X module, using API regamedll & rehlds
GNU General Public License v3.0
159 stars 104 forks source link

Add natives to check if resources are precached (model, generic, sound, event) and the pfn engine forwards #241

Open DarthMan opened 2 years ago

DarthMan commented 2 years ago

Hello. It would be cool if the pfnPrecache forwards could be added to ReAPI. I'm using fakemeta on a plugin to block precaching some game resources (that are taking up precache space but are not used on the map), to avoid the 512 limit. However, fakemeta doesn't detect resources that were precached by external AMXX plugins, and Orpheu is a bit buggy when it comes to hooking engine functions, probably because of the way ReHLDS was compiled. Adding natives to check if those resources are precached would also be cool, SourceMod already have natives for them, for example https://sourcemod.dev/#/halflife/function.IsModelPrecached I need this for an EntMod plugin, where I spawn entities with different keys and values. We can't guess all models precached by a map anyways, and if a player would spawn an entity with an unprecached model, the server would crash. So it would be really cool to have new natives added to check for precache, just as in SourceMod.

Thanks :-)

ShadowsAdi commented 2 years ago

Here is a plugin with an API:

#include <amxmodx>
#include <fakemeta>
#define PLUGIN "[Precache List]"
#define VERSION "1.0"
#define AUTHOR "Shadows Adi"

enum _:Data
{
    Resource[256],
    Type
}

enum _:Types
{
    TypeModel = 1,
    TypeSound,
    TypeGeneric
}

new Array:g_aResources
new g_iPrecachedNum[Types]

public plugin_precache()
{
    g_aResources = ArrayCreate(Data)

    register_forward(FM_PrecacheModel, "fw_PrecacheModel")
    register_forward(FM_PrecacheSound, "fw_PrecacheSound") 
    register_forward(FM_PrecacheGeneric, "fw_PrecacheGeneric") 
}

public plugin_init()
{
    register_plugin(PLUGIN, VERSION, AUTHOR)
}

public plugin_natives()
{
    register_native("precache_get_item", "native_get_item")
    register_native("precache_get_size", "native_get_size")
}

public fw_PrecacheModel(szModel[])
{
    new szTemp[Data]

    copy(szTemp[Resource], charsmax(szTemp[Resource]), szModel)
    szTemp[Type] = TypeModel

    ArrayPushArray(g_aResources, szTemp)

    g_iPrecachedNum[TypeModel]++
}

public fw_PrecacheSound(szSound[])
{
    new szTemp[Data]

    copy(szTemp[Resource], charsmax(szTemp[Resource]), szSound)
    szTemp[Type] = TypeSound
    ArrayPushArray(g_aResources, szTemp)

    g_iPrecachedNum[TypeSound]++
}

public fw_PrecacheGeneric(szResource[])
{
    new szTemp[Data]

    copy(szTemp[Resource], charsmax(szTemp[Resource]), szResource)
    szTemp[Type] = TypeGeneric
    ArrayPushArray(g_aResources, szTemp)

    g_iPrecachedNum[TypeGeneric]++
}

public plugin_end()
{
    ArrayDestroy(g_aResources)
}

// native precache_get_item(iNum, szItem[], iLen, iType)
public native_get_item(iPluginID, iParamNum)
{
    if(iParamNum != 4)
    {
        log_error(AMX_ERR_NATIVE, "%s Incorrect param num. Valid: iNum, szItem[], iLen, iType", PLUGIN)
        return -1
    }

    new szTemp[Data]
    new iNum = get_param(1)
    new iSize = get_param(3)
    new iType = get_param(4)

    do
    {
        ArrayGetArray(g_aResources, iNum, szTemp)
        iNum += 1
    } 
    while(iType != szTemp[Type])

    set_string(2, szTemp, iSize)
    return 1
}

// native precache_get_size(iType)
public native_get_size(iPluginID, iParamNum)
{
    return g_iPrecachedNum[get_param(1)]
}

Usage of API:

/* Sublime AMXX Editor v4.2 */

#include <amxmodx>

native precache_get_item(iNum, szItem[], iLen, iType)
native precache_get_size(iType)

enum
{
    TypeModel = 1,
    TypeSound,
    TypeGeneric
}

#define PLUGIN  "Test [Precache list]"
#define VERSION "1.0"
#define AUTHOR  "Shadows Adi"

public plugin_init()
{
    register_plugin(PLUGIN, VERSION, AUTHOR)

    register_concmd("debugs", "show_precache")
}

public show_precache(id)
{
    new iSize = precache_get_size(TypeModel)
    new szTemp[256]

    for(new i; i < iSize; i++)
    {
        precache_get_item(i, szTemp, charsmax(szTemp), TypeModel)

        log_to_file("debugs.log", "Model: %s", szTemp)
    }

    iSize = precache_get_size(TypeSound)

    for(new i; i < iSize; i++)
    {
        precache_get_item(i, szTemp, charsmax(szTemp), TypeSound)

        log_to_file("debugs.log", "Sound: %s", szTemp)
    }

    iSize = precache_get_size(TypeGeneric)

    for(new i; i < iSize; i++)
    {
        precache_get_item(i, szTemp, charsmax(szTemp), TypeGeneric)

        log_to_file("debugs.log", "Generic: %s", szTemp)
    }
}
DarthMan commented 2 years ago

This can only detect resources precached by the engine, not from external plugins, because it is using Fakemeta. So if I precache something this plugin will not detect it.

ShadowsAdi commented 2 years ago

Try this one. You have the test plugin above.

#include <amxmodx>
#include <orpheu>
#include <orpheu_stocks>
#include <fakemeta>

#define PLUGIN "[Precache List]"
#define VERSION "1.1"
#define AUTHOR "Shadows Adi"

enum _:Data
{
    Resource[256],
    Type
}

enum _:Types
{
    TypeModel = 1,
    TypeSound,
    TypeGeneric
}

new Array:g_aResources
new Trie:g_tResources
new g_iPrecachedNum[Types]

public plugin_precache()
{
    g_aResources = ArrayCreate(Data)
    g_tResources = TrieCreate()

    OrpheuRegisterHook(OrpheuGetEngineFunction("pfnPrecacheGeneric", "PrecacheGeneric"), "fw_PrecacheGeneric", OrpheuHookPre)
    OrpheuRegisterHook(OrpheuGetEngineFunction("pfnPrecacheModel", "PrecacheModel"), "fw_PrecacheModel", OrpheuHookPre)
    OrpheuRegisterHook(OrpheuGetEngineFunction("pfnPrecacheSound", "PrecacheSound"), "fw_PrecacheSound", OrpheuHookPre)

    register_forward(FM_PrecacheModel, "fw_PrecacheModel")
    register_forward(FM_PrecacheSound, "fw_PrecacheSound") 
    register_forward(FM_PrecacheGeneric, "fw_PrecacheGeneric")
}

public plugin_init()
{
    register_plugin(PLUGIN, VERSION, AUTHOR)
}

public plugin_natives()
{
    register_native("precache_get_item", "native_get_item")
    register_native("precache_get_size", "native_get_size")
}

public fw_PrecacheModel(szModel[])
{
    new szTemp[Data]

    copy(szTemp[Resource], charsmax(szTemp[Resource]), szModel)
    szTemp[Type] = TypeModel

    if(!TrieKeyExists(g_tResources, szTemp[Resource]))
    {
        ArrayPushArray(g_aResources, szTemp)
        TrieSetString(g_tResources, szTemp[Resource], szTemp[Type])
        g_iPrecachedNum[TypeModel]++
    }
}

public fw_PrecacheSound(szSound[])
{
    new szTemp[Data]

    copy(szTemp[Resource], charsmax(szTemp[Resource]), szSound)
    szTemp[Type] = TypeSound

    if(!TrieKeyExists(g_tResources, szTemp[Resource]))
    {
        ArrayPushArray(g_aResources, szTemp)
        TrieSetString(g_tResources, szTemp[Resource], szTemp[Type])
        g_iPrecachedNum[TypeSound]++
    }
}

public fw_PrecacheGeneric(szResource[])
{
    new szTemp[Data]

    copy(szTemp[Resource], charsmax(szTemp[Resource]), szResource)
    szTemp[Type] = TypeGeneric

    if(!TrieKeyExists(g_tResources, szTemp[Resource]))
    {
        ArrayPushArray(g_aResources, szTemp)
        TrieSetString(g_tResources, szTemp[Resource], szTemp[Type])
        g_iPrecachedNum[TypeGeneric]++
    }
}

public plugin_end()
{
    ArrayDestroy(g_aResources)
    TrieDestroy(g_tResources)
}

// native precache_get_item(iNum, szItem[], iLen, iType)
public native_get_item(iPluginID, iParamNum)
{
    if(iParamNum != 4)
    {
        log_error(AMX_ERR_NATIVE, "%s Incorrect param num. Valid: iNum, szItem[], iLen, iType", PLUGIN)
        return -1
    }

    new szTemp[Data]
    new iNum = get_param(1)
    new iSize = get_param(3)
    new iType = get_param(4)

    do
    {
        ArrayGetArray(g_aResources, iNum, szTemp)
        iNum += 1
    } 
    while(iType != szTemp[Type])

    set_string(2, szTemp, iSize)
    return 1
}

// native precache_get_size(iType)
public native_get_size(iPluginID, iParamNum)
{
    return g_iPrecachedNum[get_param(1)]
}

Orpheu functions signatures ( engine version: https://github.com/dreamstalker/rehlds/actions/runs/1838133332 ):

PF_precache_generic_I:

{
    "name"      : "PF_precache_generic_I",
    "library"   : "engine",
    "arguments" : 
    [
        {
            "type" : "char *" 
        }
    ],
    "identifiers" : 
    [
        {
            "os"    : "windows",
            "value" : [0x55, 0x8B, "*", 0x83, "*", 0x50, 0xA1, 0xC8, 0x53, "*", 0x04, 0x33, 0xC5, 0x89, 0x45, 0xFC]
        },
        {
            "os"    : "linux",
            "value" : 0x4D5D0
        }
    ]
}

PF_precache_model_I:

{
    "name"      : "PF_precache_model_I",
    "library"   : "engine",
    "arguments" : 
    [
        {
            "type" : "char *" 
        }
    ],
    "identifiers" : 
    [
        {
            "os"    : "windows",
            "value" : [0x55, 0x8B, "*", 0x53, 0x56, 0x57, 0x8B, 0x7D, 0x08, 0x33, "*", 0x85, "*", 0x0F, 0x84, 0xE3]
        },
        {
            "os"    : "linux",
            "value" : 0x7B0F0
        }
    ]
}

PF_precache_sound_I:

{
    "name"      : "PF_precache_sound_I",
    "library"   : "engine",
    "arguments" : 
    [
        {
            "type" : "char *" 
        }
    ],
    "identifiers" : 
    [
        {
            "os"    : "windows",
            "value" : [0x55, 0x8B, "*", 0x56, 0x57, 0x8B, 0x7D, "*", 0x85, "*", 0x0F, 0x84, 0x84]
        },
        {
            "os"    : "linux",
            "value" : 0x7B3C0
        }
    ]
}
ShadowsAdi commented 2 years ago

If you're still interested, I've just made this plugin which catches all resources precached: https://github.com/ShadowsAdi/PrecacheList

DarthMan commented 2 years ago

If you're still interested, I've just made this plugin which catches all resources precached: https://github.com/ShadowsAdi/PrecacheList

Good job