Skip to content

Commit 5ea096e

Browse files
authored
Support reading legacy sendprop arrays (#1550)
This rounds out the work started in #1548 to complete support for reading the older SendPropArray type array netprops, along with bringing SDKTools' GameRule netprop code in sync with core to add string array support. There aren't many SendPropArray type props around but this opens up a few interesting opportunities for plugin developers, particularly in L4D2 with manipulation of the EMS HUD. Tested reading the `m_vCPPositions` array in TF2, and reading/writing the `m_szScriptedHUDStringSet` EMS HUD netprop in L4D2. Closes #1386.
1 parent f4ff2ad commit 5ea096e

File tree

3 files changed

+128
-53
lines changed

3 files changed

+128
-53
lines changed

core/smn_entities.cpp

+31
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,37 @@ static cell_t SetEntDataString(IPluginContext *pContext, const cell_t *params)
12171217
} \
12181218
break; \
12191219
} \
1220+
case DPT_Array: \
1221+
{ \
1222+
int elementCount = pProp->GetNumElements(); \
1223+
int elementStride = pProp->GetElementStride(); \
1224+
if (element < 0 || element >= elementCount) \
1225+
{ \
1226+
return pContext->ThrowNativeError("Element %d is out of bounds (Prop %s has %d elements).", \
1227+
element, \
1228+
prop, \
1229+
elementCount); \
1230+
} \
1231+
\
1232+
pProp = pProp->GetArrayProp(); \
1233+
if (!pProp) { \
1234+
return pContext->ThrowNativeError("Error looking up ArrayProp for prop %s", \
1235+
prop); \
1236+
} \
1237+
\
1238+
if (pProp->GetType() != type) \
1239+
{ \
1240+
return pContext->ThrowNativeError("SendProp %s type is not " type_name " ([%d,%d] != %d)", \
1241+
prop, \
1242+
pProp->GetType(), \
1243+
pProp->m_nBits, \
1244+
type); \
1245+
} \
1246+
\
1247+
offset += pProp->GetOffset() + (elementStride * element); \
1248+
bit_count = pProp->m_nBits; \
1249+
break; \
1250+
} \
12201251
case DPT_DataTable: \
12211252
{ \
12221253
FIND_PROP_SEND_IN_SENDTABLE(info, pProp, element, type, type_name); \

extensions/sdktools/gamerulesnatives.cpp

+93-51
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,45 @@ enum PropFieldType
108108
{ \
109109
case type: \
110110
{ \
111-
if (element > 0) \
111+
if (element != 0) \
112112
{ \
113113
return pContext->ThrowNativeError("SendProp %s is not an array. Element %d is invalid.", \
114114
prop, \
115115
element); \
116116
} \
117117
break; \
118118
} \
119+
case DPT_Array: \
120+
{ \
121+
int elementCount = pProp->GetNumElements(); \
122+
int elementStride = pProp->GetElementStride(); \
123+
if (element < 0 || element >= elementCount) \
124+
{ \
125+
return pContext->ThrowNativeError("Element %d is out of bounds (Prop %s has %d elements).", \
126+
element, \
127+
prop, \
128+
elementCount); \
129+
} \
130+
\
131+
pProp = pProp->GetArrayProp(); \
132+
if (!pProp) { \
133+
return pContext->ThrowNativeError("Error looking up ArrayProp for prop %s", \
134+
prop); \
135+
} \
136+
\
137+
if (pProp->GetType() != type) \
138+
{ \
139+
return pContext->ThrowNativeError("SendProp %s type is not " type_name " ([%d,%d] != %d)", \
140+
prop, \
141+
pProp->GetType(), \
142+
pProp->m_nBits, \
143+
type); \
144+
} \
145+
\
146+
offset += pProp->GetOffset() + (elementStride * element); \
147+
bit_count = pProp->m_nBits; \
148+
break; \
149+
} \
119150
case DPT_DataTable: \
120151
{ \
121152
FIND_PROP_SEND_IN_SENDTABLE(info, pProp, element, type, type_name); \
@@ -142,7 +173,7 @@ enum PropFieldType
142173
} \
143174
\
144175
int elementCount = pTable->GetNumProps(); \
145-
if (element >= elementCount) \
176+
if (element < 0 || element >= elementCount) \
146177
{ \
147178
return pContext->ThrowNativeError("Element %d is out of bounds (Prop %s has %d elements).", \
148179
element, \
@@ -507,6 +538,13 @@ static cell_t GameRules_GetPropString(IPluginContext *pContext, const cell_t *pa
507538
{
508539
char *prop;
509540
int offset;
541+
int bit_count;
542+
543+
int element = 0;
544+
if (params[0] >= 4)
545+
{
546+
element = params[4];
547+
}
510548

511549
void *pGameRules = GameRules();
512550

@@ -515,56 +553,43 @@ static cell_t GameRules_GetPropString(IPluginContext *pContext, const cell_t *pa
515553

516554
pContext->LocalToString(params[1], &prop);
517555

518-
sm_sendprop_info_t info;
519-
if (!gamehelpers->FindSendPropInfo(g_szGameRulesProxy, prop, &info))
520-
{
521-
return pContext->ThrowNativeError("Property \"%s\" not found on the gamerules proxy", prop);
522-
}
523-
524-
offset = info.actual_offset;
525-
526-
if (info.prop->GetType() != DPT_String)
527-
{
528-
return pContext->ThrowNativeError("SendProp %s type is not a string (%d != %d)",
529-
prop,
530-
info.prop->GetType(),
531-
DPT_String);
532-
}
556+
FIND_PROP_SEND(DPT_String, "string");
533557

534-
size_t len;
535558
const char *src;
536-
537-
src = (char *)((intptr_t)pGameRules + offset);
538-
539-
pContext->StringToLocalUTF8(params[2], params[3], src, &len);
540-
541-
return len;
542-
}
543-
544-
// From sm_stringutil
545-
inline int strncopy(char *dest, const char *src, size_t count)
546-
{
547-
if (!count)
559+
if (pProp->GetProxyFn())
560+
{
561+
DVariant var;
562+
pProp->GetProxyFn()(pProp, pGameRules, (const void *)((intptr_t)pGameRules + offset), &var, element, 0 /* TODO */);
563+
src = var.m_pString;
564+
}
565+
else
548566
{
549-
return 0;
567+
src = *(char **)((uint8_t *)pGameRules + offset);
550568
}
551569

552-
char *start = dest;
553-
while ((*src) && (--count))
570+
if (src)
554571
{
555-
*dest++ = *src++;
572+
size_t len;
573+
pContext->StringToLocalUTF8(params[2], params[3], src, &len);
574+
return len;
556575
}
557-
*dest = '\0';
558576

559-
return (dest - start);
577+
pContext->StringToLocal(params[2], params[3], "");
578+
return 0;
560579
}
561-
//
562580

563581
static cell_t GameRules_SetPropString(IPluginContext *pContext, const cell_t *params)
564582
{
565583
char *prop;
566584
int offset;
567585
int maxlen;
586+
int bit_count;
587+
588+
int element = 0;
589+
if (params[0] >= 4)
590+
{
591+
element = params[4];
592+
}
568593

569594
void *pGameRules = GameRules();
570595

@@ -577,29 +602,46 @@ static cell_t GameRules_SetPropString(IPluginContext *pContext, const cell_t *pa
577602

578603
pContext->LocalToString(params[1], &prop);
579604

580-
sm_sendprop_info_t info;
581-
if (!gamehelpers->FindSendPropInfo(g_szGameRulesProxy, prop, &info))
605+
#if SOURCE_ENGINE == SE_CSGO
606+
if (!g_SdkTools.CanSetCSGOEntProp(prop))
582607
{
583-
return pContext->ThrowNativeError("Property \"%s\" not found on the gamerules proxy", prop);
608+
return pContext->ThrowNativeError("Cannot set ent prop %s with core.cfg option \"FollowCSGOServerGuidelines\" enabled.", prop);
584609
}
585-
586-
offset = info.actual_offset;
587-
588-
if (info.prop->GetType() != DPT_String)
610+
#endif
611+
612+
FIND_PROP_SEND(DPT_String, "string");
613+
614+
bool bIsStringIndex = false;
615+
if (pProp->GetProxyFn())
589616
{
590-
return pContext->ThrowNativeError("SendProp %s type is not a string (%d != %d)",
591-
prop,
592-
info.prop->GetType(),
593-
DPT_String);
617+
DVariant var;
618+
pProp->GetProxyFn()(pProp, pGameRules, (const void *)((intptr_t)pGameRules + offset), &var, element, 0 /* TODO */);
619+
if (var.m_pString == ((string_t *)((intptr_t)pGameRules + offset))->ToCStr())
620+
{
621+
bIsStringIndex = true;
622+
}
594623
}
595624

625+
// Only used if not string index.
626+
// TODO: If we're writing to a DPT_Array, we should use the element stride here.
596627
maxlen = DT_MAX_STRING_BUFFERSIZE;
597628

598629
char *src;
599-
char *dest = (char *)((intptr_t)pGameRules + offset);
600-
630+
size_t len;
601631
pContext->LocalToString(params[2], &src);
602-
size_t len = strncopy(dest, src, maxlen);
632+
633+
if (bIsStringIndex)
634+
{
635+
return pContext->ThrowNativeError("Setting string_t gamerules prop %s not supported yet.", prop);
636+
637+
// *(string_t *)((intptr_t)pGameRules + offset) = g_HL2.AllocPooledString(src);
638+
// len = strlen(src);
639+
}
640+
else
641+
{
642+
char *dest = (char *)((uint8_t *)pGameRules + offset);
643+
len = ke::SafeStrcpy(dest, maxlen, src);
644+
}
603645

604646
edict_t *proxyEdict = gamehelpers->EdictOfIndex(gamehelpers->EntityToBCompatRef(pProxy));
605647
if (proxyEdict != NULL)

plugins/include/sdktools_gamerules.inc

+4-2
Original file line numberDiff line numberDiff line change
@@ -169,21 +169,23 @@ native void GameRules_SetPropVector(const char[] prop, const float vec[3], int e
169169
* @param prop Property to use.
170170
* @param buffer Destination string buffer.
171171
* @param maxlen Maximum length of output string buffer.
172+
* @param element Element # (starting from 0) if property is an array.
172173
* @return Number of non-null bytes written.
173174
* @error Prop type is not a string, or lack of mod support.
174175
*/
175-
native int GameRules_GetPropString(const char[] prop, char[] buffer, int maxlen);
176+
native int GameRules_GetPropString(const char[] prop, char[] buffer, int maxlen, int element=0);
176177

177178
/**
178179
* Sets a gamerules property as a string.
179180
*
180181
* @param prop Property to use.
181182
* @param buffer String to set.
182183
* @param changeState This parameter is ignored.
184+
* @param element Element # (starting from 0) if property is an array.
183185
* @return Number of non-null bytes written.
184186
* @error Prop type is not a string, or lack of mod support.
185187
*/
186-
native int GameRules_SetPropString(const char[] prop, const char[] buffer, bool changeState=false);
188+
native int GameRules_SetPropString(const char[] prop, const char[] buffer, bool changeState=false, int element=0);
187189

188190
/**
189191
* Gets the current round state.

0 commit comments

Comments
 (0)