Archive for April, 2007

On Timer Design, Part 3 of 3

Monday, April 30th, 2007

Today let’s finish up two previous articles on timer design.

As we learned, SourceMod drops features of previous timer designs in order to make it as streamlined as possible. Timers have one unchangeable interval. If you want them to repeat, they repeat until you explicitly tell them to stop. You can slide an optional integer through the callback, but no arrays with strange copyback expectations or parameter re-ordering messes. This design doesn’t restrict you. It simply means that you have to use the other primitives of SourceMod as building blocks for your scripts.

To read on, see today’s wiki article, Timers (SourceMod Scripting), where various common examples are addressed:

  • One-time timers
  • Killing timers without using a crutch like AMX Mod X’s task_exists()
  • Counting repeatable timers
  • Passing basic data to a timer
  • Passing advanced data to a timer

Writing a JIT, Part 6 – Helpers

Friday, April 27th, 2007

I have just completed a move from Rhode Island to Massachusetts. The devlog should be back in action again, with M-W-F updates.

Today we’ve released two tools we used to write the JIT.

jit_helpers.h
This a simple class which lets us do two pass JIT writing. It stores an input stream and an output stream. The output stream can be NULL, which is used for the first pass to calculate buffer sizes. The second pass sets the output stream to a valid memory block, in which case writes physically occur.

The input and output streams are mutually exclusive; the output stream is used for code generation. The input stream is used for reading data from the source p-code. They should never collide.

x86_macros.h
This is a long file which provides jit_helpers macros for writing x86 opcodes. Be warned – the macros are not very consistent, and if you don’t know x86 encoding rules, you won’t catch idiosyncracies like needing to use IA32_Sub_RmEBP_Imm8_Disp_Reg instead of IA32_Sub_Rm_Imm8_Disp32.

However, this provides us with a very simple way of generating opcodes while keeping the multipass system transparent, and abstracting the actual encoding of x86 features, such as Mod R/M and SIB.

Let’s break down a simple macro to see how they’re built:

#define IA32_NOT_RM				0xF7	// encoding is /2
 
inline jit_uint8_t ia32_modrm(jit_uint8_t mode, jit_uint8_t reg, jit_uint8_t rm)
{
	jit_uint8_t modrm = (mode << 6);
	modrm |= (reg << 3);
	modrm |= (rm);
	return modrm;
}
 
inline void IA32_Not_Rm(JitWriter *jit, jit_uint8_t reg, jit_uint8_t mode)
{
	jit->write_ubyte(IA32_NOT_RM);
	jit->write_ubyte(ia32_modrm(mode, 2, reg));
}

First we find the encoding using the NASM manual, which is usually correct, but sometimes needs to be verified. /2 means one argument encoded as Mod R/M, which the “R” bits being 2. We write the opcode as a ubyte, then use the ia32_modrm macro to encode the R/M byte. Notice that this macro does not accept a displacement — if we came across a situation where we needed displacement, we’d add an extra parameter. Using displacement modes with this macro, as it is, would result in malformed code generation.

So, how does this end up looking in our JIT? Here’s a quick example:

JitWriter jw;
JitWriter *jit = &jw;
jw.outbase = NULL;
 
multipass:
   jw.outptr = jw.outbase;
   IA32_Not_Rm(jit, REG_EAX, MOD_REG);
 
   if (jw.outbase == NULL)
   {
      /* outptr will now have the size */
      jw.outbase = VirtualAlloc(NULL, (size_t)jw.outptr, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
      goto multipass;
   }

This snippet of code compiles one opcode using exactly the amount of memory required.

Note: It appears the license on these two files is still internal; it will be switched to the GPL shortly.

Function Calling API Wiki Article

Wednesday, April 18th, 2007

Today’s article is a wiki article on Function Calling API (SourceMod Scripting).

This has been covered here before; this article briefly explains the difference between forwards and single calls, and the difference between global and private forwards. Most importantly, it demonstrates the plugin-level API for managing both.

Creating Natives (Scripting)

Monday, April 16th, 2007

Today’s addition is another update to the SDK and wiki: Creating Natives (SourceMod Scripting).

This article goes in depth about creating natives (“dynamic” or “fake” natives) from plugins, for other plugins to use. This was discussed previously here.

Scripting FAQ?

Friday, April 13th, 2007

After observing people attempt to write scripts in SourceMod over the past week or two, the same issues/questions have come up a few times. This is a quick summary of them.

Q: Do I need to unhook/free everything in OnPluginEnd?
A: No. Handles are automatically closed, and any data used by your plugin is cleaned up.

Q: Do I need to include every .inc I use?
A: No. If the API is stable, it will be automatically included from sourcemod.inc. You can check sourcemod.inc to verify this.

Q: Can I get cvar values in OnPluginStart?
A: If your plugin is loaded after map start, yes. If your plugin is loaded automatically, it will be before server.cfg is executed. Thus, cvars won’t be set to their final values yet. If necessary, you should define convar change hooks, or start a timer via CreateTimer.

Q: Is ClientDisconnect called on mapchange?
A: Yes.

Q: What’s the difference between a user id, a client index, an edict/entity index, and a CBaseEntity?
A: A CBaseEntity is networked via an entity – and every entity has an index. For the purposes of SourceMod, a CBaseEntity, entity, edict, and edict index are all the same thing. Additionally, a client index is also an entity index in SourceMod. In some places in the Source engine, a client index is the entity index minus one — but that technicality is discarded for simplicity. Lastly, a user id is a unique, 2 byte integer assigned to every user who enters the server. It increases by one for each player that joins.

Q: Should you save the result of FindSendPropOffs/FindDataMapOffs?
A: You don’t need to — it’s a very tiny optimization. The SetProp/GetProp functions use a very fast caching mechanism.

Changing Player Names in Source

Wednesday, April 4th, 2007

Unfortunately I got back from BuyLAN a day late so I missed my Monday deadline. I wrote this article on the plane back about an issue I had to research a few weeks ago: changing names server-side in Source.

In Half-Life 1, it was very easy to modify player names server-side. Each client has an infobuffer associated with their edict, and this buffer contains a key/value pair for their display name. This value can be easily retrieved and modified using engine functions, meaning you can change a player’s displayed name without forcing a name change client-side.

In Half-Life 2, infobuffers do not exist. Instead, there is a CGameClient structure engine-side which maintains a KeyValues instance for the few server-side client properties that exist. Unlike the infobuffers of HL1, there is only a get function: IVEngineServer::GetClientConVarValue(). You cannot modify these property values in HL2.

When a player’s name changes, the engine caches the value internally, and then calls IServerGameClients::ClientSettingsChanged. The GameDLL extracts the cached name via the aforementioned key/value function, and then squirrels it away in CBasePlayer. Here’s where the design gets murky.

For the SDK, you’ll notice that on the client-side, names are displayed via the HL2 Client Engine. I.e., it uses the name cached in the server’s engine, not in the server’s mod. This isn’t a problem since the SDK is not expecting the name to ever change except through the client’s console. But there are cases when names do change without the client knowing; an example of this would be name-change denial in CS:S, via rate limiting or changing names when dead. In these cases, the mod must be sure to not use the engine’s cached display name, but to instead network its own name property. Since the mod has no way of changing the engine’s cached name, it can’t rely on it to be accurate.

All of this makes server-side name changing for plugins very difficult. There are essentially two hurdles to jump:

  1. The mod might be using the engine’s display name. This is stored deep in engine internals and is not easy to access.
  2. The mod will also be using its own name property, which may or may not have certain rules for changing. It also might not even be networked, which is the case for the SDK.

Imagine this scenario. You find the address for CGameClient::SetPlayerName in the engine binary and call it. It changes the engine’s cached name from “X” to “Y.” Then, it calls IServerGameClients::ClientSettingsChanged. The GameDLL denies the name change. Now, the GameDLL has name “X” and the engine has “Y” — you’ve created a discrepancy that will result in undefined behaviour across mods.

Thus, to make sure the name change always works, you must make sure that IServerGameClients::ClientSettingsChanged is not called for name changes. Unfortunately, you still have to tell the GameDLL that the name has been changed, and that will always be mod dependent. For example, an SDK and CS:S compatible solution might include:

void SetPlayerName(edict_t *pEdict, const char *newName)
{
     IPlayerInfo *pInfo = playerinfo->GetPlayerInfo(pEdict);
     strcpy((char *)pInfo->GetName(), newName);
}

Of course, if you know your mod will never use the engine’s cached name, you don’t have to bother doing the extensive engine reverse engineering to find the name changing function. However, if you want to be compatible across as many mods as possible, be prepared to spend a lot of time on changing names.