diff --git a/core/HalfLife2.cpp b/core/HalfLife2.cpp index 0f4695b3..83efe985 100644 --- a/core/HalfLife2.cpp +++ b/core/HalfLife2.cpp @@ -1277,3 +1277,37 @@ bool CHalfLife2::IsMapValid(const char *map) return FindMap(szTmp, sizeof(szTmp)) != SMFindMapResult::NotFound; } + +string_t CHalfLife2::AllocPooledString(const char *pszValue) +{ + // This is admittedly a giant hack, but it's a relatively safe method for + // inserting a string into the game's string pool that isn't likely to break. + // + // We find the first valid ent (should always be worldspawn), save off it's + // current targetname string_t, set it to our string to insert via SetKeyValue, + // read back the new targetname value, restore the old value, and return the new one. + + CBaseEntity *pEntity = ((IServerUnknown *) servertools->FirstEntity())->GetBaseEntity(); + auto *pNetworkable = ((IServerUnknown *) pEntity)->GetNetworkable(); + assert(pNetworkable); + + auto pServerClass = pNetworkable->GetServerClass(); + assert(pServerClass); + + static int offset = -1; + if (offset == -1) + { + sm_sendprop_info_t info; + bool found = UTIL_FindInSendTable(pServerClass->m_pTable, "m_iName", &info, 0); + assert(found); + offset = info.actual_offset; + } + + string_t *pProp = (string_t *) ((intp) pEntity + offset); + string_t backup = *pProp; + servertools->SetKeyValue(pEntity, "targetname", pszValue); + string_t newString = *pProp; + *pProp = backup; + + return newString; +} diff --git a/core/HalfLife2.h b/core/HalfLife2.h index f48f687d..a4abd4ff 100644 --- a/core/HalfLife2.h +++ b/core/HalfLife2.h @@ -47,9 +47,7 @@ #include #include #include -#if SOURCE_ENGINE >= SE_PORTAL2 #include -#endif class CCommand; @@ -185,6 +183,7 @@ public: //IGameHelpers const char *GetEntityClassname(CBaseEntity *pEntity); bool IsMapValid(const char *map); SMFindMapResult FindMap(char *pMapName, int nMapNameMax); + string_t AllocPooledString(const char *pszValue); public: void AddToFakeCliCmdQueue(int client, int userid, const char *cmd); void ProcessFakeCliCmdQueue(); diff --git a/core/smn_entities.cpp b/core/smn_entities.cpp index c1b17d74..edcc2ad8 100644 --- a/core/smn_entities.cpp +++ b/core/smn_entities.cpp @@ -2020,6 +2020,13 @@ static cell_t SetEntPropString(IPluginContext *pContext, const cell_t *params) int offset; int maxlen; edict_t *pEdict; + bool bIsStringIndex; + + int element = 0; + if (params[0] >= 5) + { + element = params[5]; + } if (!IndexToAThings(params[1], &pEntity, &pEdict)) { @@ -2043,17 +2050,31 @@ static cell_t SetEntPropString(IPluginContext *pContext, const cell_t *params) } typedescription_t *td = info.prop; - if (td->fieldType != FIELD_CHARACTER) + if (td->fieldType != FIELD_CHARACTER + && td->fieldType != FIELD_STRING + && td->fieldType != FIELD_MODELNAME + && td->fieldType != FIELD_SOUNDNAME) { return pContext->ThrowNativeError("Property \"%s\" is not a valid string", prop); } + offset = info.actual_offset; - maxlen = td->fieldSize; + + bIsStringIndex = (td->fieldType != FIELD_CHARACTER); + if (bIsStringIndex) + { + offset += (element * (td->fieldSizeInBytes / td->fieldSize)); + } + else + { + maxlen = td->fieldSize; + } + break; } case Prop_Send: { - char *prop; + sm_sendprop_info_t info; IServerUnknown *pUnk = (IServerUnknown *)pEntity; IServerNetworkable *pNet = pUnk->GetNetworkable(); if (!pNet) @@ -2061,17 +2082,37 @@ static cell_t SetEntPropString(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("The edict is not networkable"); } pContext->LocalToString(params[3], &prop); - SendProp *pSend = g_HL2.FindInSendTable(pNet->GetServerClass()->GetName(), prop); - if (!pSend) + if (!g_HL2.FindSendPropInfo(pNet->GetServerClass()->GetName(), prop, &info)) { return pContext->ThrowNativeError("Property \"%s\" not found for entity %d", prop, params[1]); } - if (pSend->GetType() != DPT_String) + + offset = info.prop->GetOffset(); + + if (info.prop->GetType() != DPT_String) { return pContext->ThrowNativeError("Property \"%s\" is not a valid string", prop); } - offset = pSend->GetOffset(); - maxlen = DT_MAX_STRING_BUFFERSIZE; + else if (element != 0) + { + return pContext->ThrowNativeError("SendProp %s is not an array. Element %d is invalid.", + prop, + element); + } + + if (info.prop->GetProxyFn()) + { + DVariant var; + info.prop->GetProxyFn()(info.prop, pEntity, (const void *) ((intptr_t) pEntity + offset), &var, element, params[1]); + if (var.m_pString == ((string_t *) ((intptr_t) pEntity + offset))->ToCStr()) + { + bIsStringIndex = true; + } + } + else + { + maxlen = DT_MAX_STRING_BUFFERSIZE; + } break; } default: @@ -2081,10 +2122,19 @@ static cell_t SetEntPropString(IPluginContext *pContext, const cell_t *params) } char *src; - char *dest = (char *)((uint8_t *)pEntity + offset); - + size_t len; pContext->LocalToString(params[4], &src); - size_t len = strncopy(dest, src, maxlen); + + if (bIsStringIndex) + { + *(string_t *) ((intptr_t) pEntity + offset) = g_HL2.AllocPooledString(src); + len = strlen(src); + } + else + { + char *dest = (char *) ((uint8_t *) pEntity + offset); + len = strncopy(dest, src, maxlen); + } if (params[2] == Prop_Send && (pEdict != NULL)) { diff --git a/plugins/include/entity.inc b/plugins/include/entity.inc index c9d92e47..fabc9d0e 100644 --- a/plugins/include/entity.inc +++ b/plugins/include/entity.inc @@ -650,18 +650,16 @@ native GetEntPropString(entity, PropType:type, const String:prop[], String:buffe /** * Sets a network property as a string. - * - * This cannot set property fields of type PropField_String_T (such as "m_target"). - * To set such fields, you should use DispatchKeyValue() from SDKTools. * * @param entity Edict index. * @param type Property type. * @param prop Property to use. * @param buffer String to set. + * @param element Element # (starting from 0) if property is an array. * @return Number of non-null bytes written. * @error Invalid entity, offset out of reasonable bounds, or property is not a valid string. */ -native SetEntPropString(entity, PropType:type, const String:prop[], const String:buffer[]); +native SetEntPropString(entity, PropType:type, const String:prop[], const String:buffer[], element=0); /** * Retrieves the count of values that an entity property's array can store.