Raised This Month: $12 Target: $400
 3% 

Accessing Private Data


Post New Thread Reply   
 
Thread Tools Display Modes
Author Message
GoD-Tony
Veteran Member
Join Date: Jul 2005
Old 03-05-2011 , 02:49   Accessing Private Data
Reply With Quote #1

I was talking to the crew on IRC the other day about my feature request, and they recommended I use an extension if I wanted this feature right away. I'm still new to this so hopefully I'm asking the right questions.

In my extension I have included IPlayerHelpers.h via
Code:
/* smsdk_config.h */
#define SMEXT_ENABLE_PLAYERHELPERS
For reference, this is the class I'm trying to access.
Code:
/* PlayerManager.h */
class CPlayer : public IGamePlayer
{
	friend class PlayerManager;
public:
	CPlayer();
public:
	const char *GetName();
	const char *GetIPAddress();
	const char *GetAuthString();
	edict_t *GetEdict();
	bool IsInGame();
	bool WasCountedAsInGame();
	bool IsConnected();
	bool IsAuthorized();
	bool IsFakeClient();
	void SetAdminId(AdminId id, bool temporary);
	AdminId GetAdminId();
	void Kick(const char *str);
	bool IsInKickQueue();
	IPlayerInfo *GetPlayerInfo();
	unsigned int GetLanguageId();
	int GetUserId();
	bool RunAdminCacheChecks();
	void NotifyPostAdminChecks();
	unsigned int GetSerial();
public:
	void DoBasicAdminChecks();
	void MarkAsBeingKicked();
	int GetLifeState();
private:
	void Initialize(const char *name, const char *ip, edict_t *pEntity);
	void Connect();
	void Disconnect();
	void SetName(const char *name);
	void DumpAdmin(bool deleting);
	void Authorize(const char *auth);
	void Authorize_Post();
	void DoPostConnectAuthorization();
private:
	bool m_IsConnected;
	bool m_IsInGame;
	bool m_IsAuthorized;
	bool m_bIsInKickQueue;
	String m_Name;
	String m_Ip;
	String m_IpNoPort;
	String m_AuthID;
	AdminId m_Admin;
	bool m_TempAdmin;
	edict_t *m_pEdict;
	IPlayerInfo *m_Info;
	String m_LastPassword;
	bool m_bAdminCheckSignalled;
	int m_iIndex;
	unsigned int m_LangId;
	int m_UserId;
	bool m_bFakeClient;
	serial_t m_Serial;
};
In the SM core they access m_LangId directly doing this:
Code:
/* PlayerManager.cpp */
CPlayer *pPlayer = &m_Players[client];
pPlayer->m_LangId = ...
but in my extension...
Code:
static cell_t SetClientLanguage(IPluginContext *pContext, const cell_t *params)
{
	IGamePlayer *pPlayer = playerhelpers->GetGamePlayer( params[1] );

	// This doesn't work.
	pPlayer->m_LangId = ...

	return 1;
}
What am I missing here?
GoD-Tony is offline
PM
hello, i am pm
Join Date: Jan 2004
Location: Canalization
Old 03-05-2011 , 04:12   Re: Accessing Private Data
Reply With Quote #2

Uhhh, this is not a good situation

This is how it's supposed to work in C++ :
the interface IGamePlayer would provide a public function SetLanguageId( id ).
You would do

Code:
IGamePlayer *pPlayer = playerhelpers->GetGamePlayer( params[1] );
pPlayer->SetLanguageId( blabla );
Here, as far as I can see at the moment, the interface IGamePlayer does not have such a function. The ideal solution would be to add it to sourcemod core.

Other than that, if you really want to set the field anyway, you can hack.
From here, you're using implementation details of sourcemod, which are not part of the "contract", ie. interface. This code can be broken on updates.

First of all, you'd have to use your knowledge that what playerhelpers->GetGamePlayer( ... ) returns is actually a CPlayer instance.

Code:
IGamePlayer *pPlayer = playerhelpers->GetGamePlayer( params[1] );
CPlayer *pPlayerImpl = (CPlayer*)pPlayer;
The next problem is accessing the field m_LangId. As you can see, it's in the private: section of the class. Ie. you can't access it from outside. The PlayerManager class in SourceMod can only access it because it is declared "friend" of class CPlayer: friend class PlayerManager;

You're not friend, so you have three possibilities:
1) Copy the CPlayer class declaration into your source file (and don't #include the .h file where CPlayer is declared) and change the private section containing m_LangId to public.

OR

2) Copy the CPlayer class declaration into your source file and declare your class friend.

OR

3) Copy the CPlayer class declaration into your source file and add a setter method

OR

4) Don't use the CPlayer declaration at all and access the field by a pointer offset.

I'd go with 3) which would result in the following code:

Code:
// Warning: This code may break on sourcemod updates

// Don't #include playermanager.h because we're using our own CPlayer

/* actually from PlayerManager.h */
class CPlayer : public IGamePlayer
{
public:
	CPlayer();
public:
	const char *GetName();
	const char *GetIPAddress();
	const char *GetAuthString();
	edict_t *GetEdict();
	bool IsInGame();
	bool WasCountedAsInGame();
	bool IsConnected();
	bool IsAuthorized();
	bool IsFakeClient();
	void SetAdminId(AdminId id, bool temporary);
	AdminId GetAdminId();
	void Kick(const char *str);
	bool IsInKickQueue();
	IPlayerInfo *GetPlayerInfo();
	unsigned int GetLanguageId();
	int GetUserId();
	bool RunAdminCacheChecks();
	void NotifyPostAdminChecks();
	unsigned int GetSerial();
public:
	void DoBasicAdminChecks();
	void MarkAsBeingKicked();
	int GetLifeState();
private:
	void Initialize(const char *name, const char *ip, edict_t *pEntity);
	void Connect();
	void Disconnect();
	void SetName(const char *name);
	void DumpAdmin(bool deleting);
	void Authorize(const char *auth);
	void Authorize_Post();
	void DoPostConnectAuthorization();
private:
	bool m_IsConnected;
	bool m_IsInGame;
	bool m_IsAuthorized;
	bool m_bIsInKickQueue;
	String m_Name;
	String m_Ip;
	String m_IpNoPort;
	String m_AuthID;
	AdminId m_Admin;
	bool m_TempAdmin;
	edict_t *m_pEdict;
	IPlayerInfo *m_Info;
	String m_LastPassword;
	bool m_bAdminCheckSignalled;
	int m_iIndex;
	unsigned int m_LangId;
	int m_UserId;
	bool m_bFakeClient;
	serial_t m_Serial;

public:
	void SetLanguageId(unsigned int newLangId) {
		m_LangId = newLangId;
	}
};

static cell_t SetClientLanguage(IPluginContext *pContext, const cell_t *params)
{
	IGamePlayer *pPlayer = playerhelpers->GetGamePlayer( params[1] );

	CPlayer *pPlayerHack = (CPlayer*)pPlayer;
	pPlayerHack->SetLanguageId( blablabla );

	return 1;
}
I'm in a hurry so I haven't tried to compile so I may be wrong
__________________
hello, i am pm
PM is offline
GoD-Tony
Veteran Member
Join Date: Jul 2005
Old 03-05-2011 , 05:54   Re: Accessing Private Data
Reply With Quote #3

Compiles and works!

Before posting here I was checking out some snippets and I attempted to do what you had explained, but I was using structs. It compiled but it didn't look right at all, and it completely crashed the server.

Thanks for taking the time to explain it.

Quote:
Originally Posted by PM View Post
From here, you're using implementation details of sourcemod, which are not part of the "contract", ie. interface. This code can be broken on updates.
Does this mean the code could be broken on any SourceMod update, or only updates that change the CPlayer class?
GoD-Tony is offline
PM
hello, i am pm
Join Date: Jan 2004
Location: Canalization
Old 03-05-2011 , 08:06   Re: Accessing Private Data
Reply With Quote #4

Basically, the code will break when the position of the m_LangId field relative to the beginning of the CPlayer object in memory changes.

This could happen if someone changes the class or if it is compiled with other alignment options.

Another situation that may happen would be a change in PlayerManager, so that it would not be returning CPlayer for some cases, but an instance of another class, for example a new class CBotPlayer, which would also inherit from IPlayerManager.

I guess that none of this is likely to happen, but it could ;)

Greetings,
PM

Edit:
Just for completeness, a reason why the code using a self-defined struct could have failed:
If a class itself or a class it inhertis from contains virtual functions, the first 4 (x86) or 8 (x64) bytes of the object's data will be a pointer to the 'virtual function table'. This is automatically added by the compiler and is used to resolve virtual function calls (ie. to make sure that if you call GetLanguageId on a IGamePlayer * variable, it will actually call CPlayer::GetLanguageId if the real object is a CPlayer).
If you re-create the class yourself and don't have any virtual functions (here they come from inheriting from IGamePlayer), the member variable positions won't match the member variable positions in the real class with a virtual function table pointer, and you'll be overwriting something else.
__________________
hello, i am pm

Last edited by PM; 03-05-2011 at 08:14.
PM is offline
BAILOPAN
Join Date: Jan 2004
Old 03-06-2011 , 14:02   Re: Accessing Private Data
Reply With Quote #5

GoD-Tony: Whoa! If you're going this far to work around it, you might as well just submit a patch to Core. I can do a fast review & checkin. Something this hacky is bound to break and I don't think it's a good idea to distribute an extension like that.

You've got the native, so all you need is to put it in the right place (smn_lang.cpp). If you need help, let me know - I see messages on IRC or in the bug report faster than forums.
__________________
egg
BAILOPAN is offline
GoD-Tony
Veteran Member
Join Date: Jul 2005
Old 03-06-2011 , 15:01   Re: Accessing Private Data
Reply With Quote #6

Haha, yes I pretty much got the same response on IRC once they looked at the source of the extension . I was told that if I submit a patch then this native could very well make it into 1.3.x.

I'll go that route since this native is already proving useful on my servers.

Last edited by GoD-Tony; 03-07-2011 at 10:11.
GoD-Tony is offline
Reply


Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -4. The time now is 06:32.


Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
Theme made by Freecode