Admin Account Routing Refactor

This commit is contained in:
AconiteX
2021-08-16 11:44:25 -04:00
parent 89169339c6
commit 55976968a0
11 changed files with 236 additions and 92 deletions

View File

@@ -394,19 +394,41 @@ ClientConnection::onIdValidated(bool canLogin, bool canCreateRegularCharacter, b
// Save lists of claimed rewards, which won't be used again until later in the login sequence
m_consumedRewardEvents = consumedRewardEvents;
m_claimedRewardItems = claimedRewardItems;
bool isAdmin = false;
int level = 0;
if (AdminAccountManager::isAdminAccount(Unicode::toLower(getAccountName()), level) && (level !=
0)) // Note: not checking IP, so that owners of god accounts can create characters to play from home without having to erase the characters they use for work
// Revised - SWG Source - 2021 (Aconite)
// Determine if this account is in the Admin Table
if(AdminAccountManager::getAdminLevel(Unicode::toLower(getAccountName())) > 0)
{
// Determine if we are using Secure Login (IP restricted)
if(ConfigConnectionServer::getUseSecureLoginForGodAccess())
{
// If we *require* secure login, and we are *not* from a secure IP, disconnect the client
// as they should not be inside this account
if(!AdminAccountManager::isInternalIp(getRemoteAddress()))
{
LOG("GodMode", ("[%s : %s : %s : %s] login to this admin account failed because secure login was required but the remote IP address connected was not in the allowed range.",
getAccountName().c_str(), getCharacterName().c_str(), getCharacterId().getValueString().c_str(), getRemoteAddress().c_str()));
disconnect();
return;
}
else
{
m_isSecure = true;
}
}
// If we're here, we're an admin account, and we have the correct IP (if applicable)
// so we're overriding the ClientPermissionMessage values
canLogin = true;
canCreateRegularCharacter = true;
canSkipTutorial = true;
m_isAdminAccount = true;
isAdmin = true; // this flag goes to the client to let it know we can connect to a Locked Cluster
LOG("GodMode", ("[%s : %s : %s : %s] validating this account to the Connection Server after successful authentication.",
getAccountName().c_str(), getCharacterName().c_str(), getCharacterId().getValueString().c_str(), getRemoteAddress().c_str()));
}
ClientPermissionsMessage c(canLogin, canCreateRegularCharacter, canCreateJediCharacter, canSkipTutorial);
ClientPermissionsMessage c(canLogin, canCreateRegularCharacter, canCreateJediCharacter, canSkipTutorial, isAdmin);
send(c, true);
DEBUG_REPORT_LOG(true, ("Permissions for %lu:\n", getSUID()));

View File

@@ -117,6 +117,8 @@ void ConfigConnectionServer::install(void)
KEY_STRING (altPublicBindAddress, "");
KEY_BOOL (useOldSuidGenerator, false);
KEY_BOOL (useSecureLoginForGodAccess, false);
int index = 0;
char const * result = 0;
do

View File

@@ -68,6 +68,8 @@ public:
int connectionServerNumber;
int fakeBuddyPoints;
bool useSecureLoginForGodAccess;
bool useOldSuidGenerator;
const char *altPublicBindAddress;
@@ -187,6 +189,9 @@ public:
static const char *getPublicBindAddress();
static bool getUseOldSuidGenerator();
static bool getUseSecureLoginForGodAccess();
private:
static Data *data;
};
@@ -480,4 +485,9 @@ inline bool ConfigConnectionServer::getUseOldSuidGenerator() {
return data->useOldSuidGenerator;
}
inline bool ConfigConnectionServer::getUseSecureLoginForGodAccess()
{
return data->useSecureLoginForGodAccess;
}
#endif // _ConfigConnectionServer_H

View File

@@ -943,10 +943,10 @@ LoginServer::validateAccount(const StationId &stationId, uint32 clusterId, uint3
bool clientIsInternal = false;
ClientConnection *conn = getValidatedClient(stationId);
if (conn) {
clientIsInternal = AdminAccountManager::isInternalIp(conn->getRemoteAddress());
clientIsInternal = conn->getAdminLevel() > 0;
}
if (clientIsInternal && ConfigLoginServer::getInternalBypassOnlineLimit()) {
if (clientIsInternal) {
canLogin = true;
} else if (cle->m_numPlayers <= cle->m_onlinePlayerLimit) {
canLogin = true;
@@ -968,6 +968,12 @@ LoginServer::validateAccount(const StationId &stationId, uint32 clusterId, uint3
canSkipTutorial = true;
}
// double check locked cluster
if(cle->m_locked)
{
canLogin = clientIsInternal;
}
ValidateAccountReplyMessage msg(stationId, canLogin, canCreateRegular, canCreateJedi, canSkipTutorial, track, consumedRewardEvents, claimedRewardItems);
cle->m_centralServerConnection->send(msg, true);
}
@@ -1228,32 +1234,8 @@ LoginServer::onValidateClient(StationId suid, const std::string &username, Clien
NOT_NULL(conn);
WARNING_STRICT_FATAL(getValidatedClient(suid), ("Validating an already valid client in onValidateClient(). StationId: %d UserName: %s", suid, username.c_str()));
int adminLevel = 0;
const bool isAdminAccount = AdminAccountManager::isAdminAccount(Unicode::toLower(username), adminLevel);
if (conn->getRequestedAdminSuid() != 0) {
//verify internal, secure, is on the god list
bool loginOK = false;
if (!isSecure) {
LOG("CustomerService", ("AdminLogin: User %s (account %li) attempted to log into account %li, but was not using a SecureID token", username.c_str(), suid, conn->getRequestedAdminSuid()));
} else {
if (!AdminAccountManager::isInternalIp(conn->getRemoteAddress())) {
LOG("CustomerService", ("AdminLogin: User %s (account %li) attempted to log into account %li, but was not logging in from an internal IP", username.c_str(), suid, conn->getRequestedAdminSuid()));
} else {
if (!isAdminAccount || adminLevel < 10) {
LOG("CustomerService", ("AdminLogin: User %s (account %li) attempted to log into account %li, but did not have sufficient permissions", username.c_str(), suid, conn->getRequestedAdminSuid()));
} else {
suid = conn->getRequestedAdminSuid();
loginOK = true;
}
}
}
if (!loginOK) {
conn->disconnect();
return;
}
}
// determine if this is an admin account
const bool isAdminAccount = AdminAccountManager::getAdminLevel(username) > 0;
// encrypt the clients credentials with the key, return
// the cipher text to the client for use as a connection
@@ -1297,14 +1279,11 @@ LoginServer::onValidateClient(StationId suid, const std::string &username, Clien
conn->send(k, true);
delete[] keyBuffer;
// send cluster enum
bool clientInternal = AdminAccountManager::isInternalIp(conn->getRemoteAddress());
std::vector <LoginEnumCluster::ClusterData> data;
for (ClusterListType::const_iterator j = m_clusterList.begin(); j != m_clusterList.end(); ++j) {
ClusterListEntry *cle = *j;
if (cle && cle->m_clusterId != 0 && cle->m_clusterName.size() != 0 && (clientInternal || !cle->m_secret)) {
if (cle && cle->m_clusterId != 0 && cle->m_clusterName.size() != 0 && (isAdminAccount || !cle->m_secret)) {
LoginEnumCluster::ClusterData item;
item.m_clusterId = cle->m_clusterId;
item.m_clusterName = cle->m_clusterName;
@@ -1331,7 +1310,7 @@ LoginServer::onValidateClient(StationId suid, const std::string &username, Clien
conn->setIsValidated(true);
conn->setStationId(suid);
conn->setIsSecure(isSecure);
conn->setAdminLevel(isAdminAccount ? adminLevel : -1);
conn->setAdminLevel(isAdminAccount ? AdminAccountManager::getAdminLevel(username) : -1);
IGNORE_RETURN(m_validatedClientMap.insert(std::pair<StationId, ClientConnection *>(suid, conn)));
//Must be done after setting various information in the connection object above
@@ -1408,11 +1387,16 @@ void LoginServer::sendExtendedClusterInfo(ClientConnection &client) const {
/**
* Send the list of active clusters to a client.
* Also picks a connection server for the client to use.
* @todo We'd like to resend this to all clients whenever the status
* of any servers changes
*
* Note: *** should send to individual clients - NOT all connected
* clients due to admin permissions check ***
*/
void LoginServer::sendClusterStatus(ClientConnection &conn) const {
const bool clientIsPrivate = AdminAccountManager::isInternalIp(conn.getRemoteAddress());
// Validate admin level here as clientIsPrivate. We don't need to check secure/IP because the
// connection server will force disconnect non-secure connections if they are required by configuration.
const bool clientIsPrivate = conn.getAdminLevel() > 0;
const unsigned int subscriptionBits = conn.getSubscriptionBits();
const bool isFreeTrialAccount = (((subscriptionBits & ClientSubscriptionFeature::FreeTrial) != 0) &&
((subscriptionBits & ClientSubscriptionFeature::Base) == 0));
@@ -1451,10 +1435,7 @@ void LoginServer::sendClusterStatus(ClientConnection &conn) const {
if (item.m_connectionServerPort) {
item.m_connectionServerPingPort = connServer.pingPort;
// for security/confidential information issue, only report
// population count to secured internal connections with
// admin privilege >= 10
if (clientIsPrivate && conn.getIsSecure() && (conn.getAdminLevel() >= 10)) {
if (clientIsPrivate) {
item.m_populationOnline = cle->m_numPlayers;
} else {
item.m_populationOnline = -1;
@@ -1493,10 +1474,14 @@ void LoginServer::sendClusterStatus(ClientConnection &conn) const {
} else {
item.m_status = LoginClusterStatus::ClusterData::S_loading;
}
if (cle->m_locked && !clientIsPrivate) {
if (cle->m_locked) {
item.m_status = LoginClusterStatus::ClusterData::S_locked;
} // locked takes precedence over up or loading
// flag as admin/secret (see LoginClusterStatus.h)
item.m_isAdmin = clientIsPrivate;
item.m_isSecret = cle->m_secret;
item.m_dontRecommend = (cle->m_notRecommendedDatabase || cle->m_notRecommendedCentral);
item.m_onlinePlayerLimit = cle->m_onlinePlayerLimit;
item.m_onlineFreeTrialLimit = cle->m_onlineFreeTrialLimit;

View File

@@ -501,6 +501,12 @@ void Client::addControlledObject(ServerObject &object) {
setGodMode(true);
}
}
// validate isUsingAdminLogin each onClientReady() call per SWG Source change - 2021 (Aconite)
// isUsingAdminLogin is used to check if an *account* is in the admin table
// so we can monitor admin accessed accounts *regardless* of their current god mode/level
setUsingAdminLogin(AdminAccountManager::getAdminLevel(getAccountName()) > 0);
}
// ----------------------------------------------------------------------
@@ -1937,53 +1943,131 @@ float Client::computeDeltaTimeInSeconds(uint32 const syncStampLong) const {
//-----------------------------------------------------------------------
/**
* setGodMode
* Processes the request to turn God Mode in the Client on,
* enabling admin commands and admin-treatment of a CreatureObject.
*
* @param value true = god mode on; false = god mode off
* @return true if successful, false if not
*
* Handling was rewritten by SWG Source - 2021 for applicability
* to Source/VM context
* Authors: Aconite
*/
bool Client::setGodMode(bool value) {
// (re?) check god permissions
m_godLevel = AdminAccountManager::getAdminLevel(m_accountName.c_str());
if (ConfigServerGame::getAdminGodToAll() || (m_godLevel > 0)) {
m_godValidated = true;
if (ConfigServerGame::getAdminGodToAll()) {
m_godLevel = ConfigServerGame::getAdminGodToAllGodLevel();
}
}
const bool wasInGodMode = m_godMode;
auto * creatureObject = safe_cast<CreatureObject *>(m_primaryControlledObject.getObject());
ScriptParams params;
bool wasInGodMode = m_godMode;
m_godMode = value;
if (value && !m_godValidated) {
LOG("CustomerService", ("Avatar:%s denied god mode because it wasn't validated.", PlayerObject::getAccountDescription(getCharacterObjectId()).c_str()));
m_godMode = false;
}
CreatureObject *primaryControlledObject = safe_cast<CreatureObject *>(m_primaryControlledObject.getObject());
if (!primaryControlledObject) {
if (value && !wasInGodMode) {
LOG("CustomerService", ("Avatar:%s denied god mode because it has no associated character.", m_accountName.c_str()));
}
m_godMode = false;
if(!creatureObject)
{
LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode failed because we couldn't get the CreatureObject.",
getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str()));
return false;
}
if (m_godMode) {
IGNORE_RETURN(primaryControlledObject->grantCommand(AdminAccountManager::getAdminCommandName(), false));
} else {
primaryControlledObject->revokeCommand(AdminAccountManager::getAdminCommandName(), false, true);
}
if (m_godMode != wasInGodMode) {
if (m_godMode) {
LOG("CustomerService", ("Avatar:%s granted god mode %s level %d.", PlayerObject::getAccountDescription(getCharacterObjectId()).c_str(), AdminAccountManager::getAdminCommandName(), m_godLevel));
} else {
LOG("CustomerService", ("Avatar:%s dropped god mode.", PlayerObject::getAccountDescription(getCharacterObjectId()).c_str()));
// Request to turn God Mode Off, in which case we don't need to validate permissions, because off isn't bad
if(!value)
{
// If we were already in god mode, then we've already made an authenticated request to be in god mode,
// so log the event and proceed as an authorized activity, unless a script trigger blocks the switch.
if(wasInGodMode)
{
// allow a SCRIPT_OVERRIDE to block turning god mode off if necessary
// see OnTurnedGodModeOff in script.player.base_player.java
if(creatureObject->getScriptObject()->trigAllScripts(Scripting::TRIG_ON_SET_GOD_MODE_OFF, params) != SCRIPT_CONTINUE)
{
LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode off failed because OnSetGodModeOff did not return SCRIPT_CONTINUE.",
getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str()));
return false;
}
else
{
LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode off success.",
getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str()));
}
}
// cell permissions may change for us for all cells, so observe all buildings in range for the change
CellPermissions::ViewerChangeObserver o(primaryControlledObject);
ObserveTracker::onGodModeChanged(*this);
m_godLevel = 0;
m_godMode = false;
m_godValidated = false;
// remove the characterAbility "admin" so GM commands aren't sent by the client while not in God Mode
IGNORE_RETURN(creatureObject->revokeCommand(AdminAccountManager::getAdminCommandName(), false, true));
return true;
}
return (value == m_godMode); // return true if the value was set to what was requested, false otherwise
// If we're here, it's because the request is to turn God Mode ON
// First, determine if we're using Secure Login Mode (IP-restricted)
if(ConfigServerGame::getUseSecureLoginForGodAccess())
{
if(!AdminAccountManager::isInternalIp(getIpAddress()))
{
LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode on failed because secure login was required and the connection IP was not approved.",
getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str()));
return false;
}
}
// If we aren't required to be secure, or if we are and we are secure, begin permission checks
// Start with if everyone can have God Mode, in which case we don't need to bother otherwise
int godLevel = 0;
if(ConfigServerGame::getAdminGodToAll() && ConfigServerGame::getAdminGodToAllGodLevel() > 0)
{
// Make sure the account isn't in the admin table, because that should supersede
// whatever the adminGodToAll level is.
if(AdminAccountManager::getAdminLevel(m_accountName) == 0)
{
godLevel = ConfigServerGame::getAdminGodToAllGodLevel();
}
else
{
godLevel = AdminAccountManager::getAdminLevel(m_accountName);
}
}
// We aren't giving god to everyone, so validate actual god level permissions
else
{
godLevel = AdminAccountManager::getAdminLevel(m_accountName);
}
// Grant God Mode
if(godLevel > 0)
{
// Allow a SCRIPT_OVERRIDE to block turning God Mode on via OnSetGodModeOn trigger
// see script.player.player_base.java
if(creatureObject->getScriptObject()->trigAllScripts(Scripting::TRIG_ON_SET_GOD_MODE_ON, params) != SCRIPT_CONTINUE)
{
LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode on failed because OnSetGodModeOn did not return SCRIPT_CONTINUE.",
getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str()));
return false;
}
else
{
// flags
m_godMode = true;
m_godValidated = true;
m_godLevel = godLevel;
// grant all commands with the "admin" characterAbility
IGNORE_RETURN(creatureObject->grantCommand(AdminAccountManager::getAdminCommandName(), false));
// reset observers and notify cells of potential change
CellPermissions::ViewerChangeObserver o(creatureObject);
ObserveTracker::onGodModeChanged(*this);
LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode on success.",
getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str()));
return true;
}
}
// If we're here, it's because someone tried to use /setGod who doesn't have permissions
else
{
LOG("GodMode", ("[%s : %s : %s : %s] /setGodMode on failed because the requesting account does not have permissions.",
getAccountName().c_str(), getCharacterName().c_str(), getCharacterObjectId().getValueString().c_str(), getIpAddress().c_str()));
return false;
}
}
//-----------------------------------------------------------------------
@@ -2165,12 +2249,20 @@ void Client::launchWebBrowser(std::string const &url) const {
// ----------------------------------------------------------------------
bool Client::isUsingAdminLogin() const {
bool Client::isUsingAdminLogin() const
{
return m_usingAdminLogin;
}
// ----------------------------------------------------------------------
void Client::setUsingAdminLogin(bool value)
{
m_usingAdminLogin = value;
}
// ----------------------------------------------------------------------
bool Client::shouldReceiveCombatSpam(NetworkId const &attacker, Vector const &attackerPosition_w, NetworkId const &defender, Vector const &defenderPosition_w) const {
PROFILER_AUTO_BLOCK_DEFINE("Client::shouldReceiveCombatSpam");

View File

@@ -105,6 +105,8 @@ public:
bool isUsingAdminLogin() const;
void setUsingAdminLogin(bool value);
CombatDataTable::CombatSpamFilterType getCombatSpamFilter() const;
int getCombatSpamRangeSquaredFilter() const;

View File

@@ -330,7 +330,6 @@ static const Scripting::ScriptFuncTable ScriptFuncList[] =
{Scripting::TRIG_ENTER_REGION, "OnEnterRegion", "ss"},
{Scripting::TRIG_EXIT_REGION, "OnExitRegion", "ss"},
{Scripting::TRIG_ON_PLAYER_REPORTED_CHAT, "OnPlayerReportedChat", "Ou"},
//----------------------------------------------------------------------
{Scripting::TRIG_VENDOR_ITEM_COUNT_REPLY, "OnVendorItemCountReply", "ii"},
@@ -380,6 +379,9 @@ static const Scripting::ScriptFuncTable ScriptFuncList[] =
{Scripting::TRIG_PVP_RANKING_CHANGED, "OnPvpRankingChanged", "ii"},
{Scripting::TRIG_ON_SET_GOD_MODE_ON, "OnSetGodModeOn", ""},
{Scripting::TRIG_ON_SET_GOD_MODE_OFF, "OnSetGodModeOff", ""},
//-----------------------------------------------------------------------
//-- buff builder

View File

@@ -480,7 +480,8 @@ enum TrigId
TRIG_ON_ABANDON_PLAYER_QUEST = 302,
TRIG_ON_GCW_SCORE_CATEGORY_PERCENTILE_CHANGE = 303,
TRIG_WAYPOINT_WARP = 304,
TRIG_ON_PLAYER_REPORTED_CHAT = 305,
TRIG_ON_SET_GOD_MODE_ON = 305,
TRIG_ON_SET_GOD_MODE_OFF = 306,
TRIG_LAST_TRIGGER
};

View File

@@ -10,17 +10,19 @@
// ======================================================================
ClientPermissionsMessage::ClientPermissionsMessage(bool canLogin, bool canCreateRegularCharacter, bool canCreateJediCharacter, bool canSkipTutorial) :
ClientPermissionsMessage::ClientPermissionsMessage(bool canLogin, bool canCreateRegularCharacter, bool canCreateJediCharacter, bool canSkipTutorial, bool isAdmin) :
GameNetworkMessage("ClientPermissionsMessage"),
m_canLogin(canLogin),
m_canCreateRegularCharacter(canCreateRegularCharacter),
m_canCreateJediCharacter(canCreateJediCharacter),
m_canSkipTutorial(canSkipTutorial)
m_canSkipTutorial(canSkipTutorial),
m_isAdmin(isAdmin)
{
addVariable(m_canLogin);
addVariable(m_canCreateRegularCharacter);
addVariable(m_canCreateJediCharacter);
addVariable(m_canSkipTutorial);
addVariable(m_isAdmin);
}
//-----------------------------------------------------------------------
@@ -30,12 +32,14 @@ ClientPermissionsMessage::ClientPermissionsMessage(Archive::ReadIterator & sourc
m_canLogin(),
m_canCreateRegularCharacter(),
m_canCreateJediCharacter(),
m_canSkipTutorial()
m_canSkipTutorial(),
m_isAdmin()
{
addVariable(m_canLogin);
addVariable(m_canCreateRegularCharacter);
addVariable(m_canCreateJediCharacter);
addVariable(m_canSkipTutorial);
addVariable(m_isAdmin);
unpack(source);
}

View File

@@ -24,7 +24,7 @@
class ClientPermissionsMessage : public GameNetworkMessage
{
public:
ClientPermissionsMessage(bool canLogin, bool canCreateRegularCharacter, bool canCreateJediCharacter, bool canSkipTutorial);
ClientPermissionsMessage(bool canLogin, bool canCreateRegularCharacter, bool canCreateJediCharacter, bool canSkipTutorial, bool isAdmin);
explicit ClientPermissionsMessage(Archive::ReadIterator & source);
virtual ~ClientPermissionsMessage();
@@ -33,12 +33,14 @@ class ClientPermissionsMessage : public GameNetworkMessage
bool getCanCreateRegularCharacter() const;
bool getCanCreateJediCharacter() const;
bool getCanSkipTutorial() const;
bool getIsAdmin() const;
private:
Archive::AutoVariable<bool> m_canLogin;
Archive::AutoVariable<bool> m_canCreateRegularCharacter;
Archive::AutoVariable<bool> m_canCreateJediCharacter;
Archive::AutoVariable<bool> m_canSkipTutorial;
Archive::AutoVariable<bool> m_isAdmin;
ClientPermissionsMessage();
ClientPermissionsMessage(const ClientPermissionsMessage&);
@@ -73,6 +75,13 @@ inline bool ClientPermissionsMessage::getCanSkipTutorial() const
return m_canSkipTutorial.get();
}
// ----------------------------------------------------------------------
inline bool ClientPermissionsMessage::getIsAdmin() const
{
return m_isAdmin.get();
}
// ======================================================================
#endif // _INCLUDED_ClientCentralMessages_H

View File

@@ -31,6 +31,17 @@ struct LoginClusterStatus_ClusterData
bool m_dontRecommend;
uint32 m_onlinePlayerLimit;
uint32 m_onlineFreeTrialLimit;
// This flag tells the client that the account which requested the cluster information
// is considered an admin by the server (in Admin Account Table). This enables connection
// to locked clusters and viewing secret clusters. We don't need to worry about this flag
// being manipulated because the Connection Server verifies admin permissions after the fact.
// This flag merely unblocks client-side user interface elements for locked/secret clusters.
// We also send this flag in the ClientPermissionsMessage header for character creation.
bool m_isAdmin;
// Flag so we can append (Secret) in Client (for admins)
bool m_isSecret;
};
/**
@@ -85,6 +96,8 @@ namespace Archive
get(source,c.m_dontRecommend);
get(source,c.m_onlinePlayerLimit);
get(source,c.m_onlineFreeTrialLimit);
get(source, c.m_isAdmin);
get(source,c.m_isSecret);
}
inline void put(ByteStream & target, const LoginClusterStatus_ClusterData &c)
@@ -101,6 +114,8 @@ namespace Archive
put(target,c.m_dontRecommend);
put(target,c.m_onlinePlayerLimit);
put(target,c.m_onlineFreeTrialLimit);
put(target,c.m_isAdmin);
put(target,c.m_isSecret);
}
}