1266 lines
No EOL
54 KiB
C++
1266 lines
No EOL
54 KiB
C++
/*
|
|
** Command & Conquer Renegade(tm)
|
|
** Copyright 2025 Electronic Arts Inc.
|
|
**
|
|
** This program is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 3 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/***********************************************************************************************
|
|
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
|
|
***********************************************************************************************
|
|
* *
|
|
* Project Name : Combat *
|
|
* *
|
|
* $Archive:: /Commando/Code/Commando/slavemaster.cpp $*
|
|
* *
|
|
* Author:: Steve Tall *
|
|
* *
|
|
* $Modtime:: 2/15/02 12:44p $*
|
|
* *
|
|
* $Revision:: 18 $*
|
|
* *
|
|
*---------------------------------------------------------------------------------------------*
|
|
* Functions: *
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
|
|
#include "always.h"
|
|
#include <windows.h>
|
|
#include "slavemaster.h"
|
|
#include "wwdebug.h"
|
|
#include "registry.h"
|
|
#include "_globals.h"
|
|
#include "autostart.h"
|
|
#include "ini.h"
|
|
#include "rawfile.h"
|
|
#include "inisup.h"
|
|
#include "natter.h"
|
|
#include "gamesideservercontrol.h"
|
|
#include "win.h"
|
|
#include "gamedata.h"
|
|
#include "serversettings.h"
|
|
#include "bandwidth.h"
|
|
#include "consolemode.h"
|
|
#include "specialbuilds.h"
|
|
#include "useroptions.h"
|
|
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#define KEY_NUM_SLAVES "Count"
|
|
#define KEY_SLAVE_NAME "Name"
|
|
#define KEY_SLAVE_SERIAL "Serial"
|
|
#define KEY_SLAVE_ENABLE "Enable"
|
|
#define KEY_SLAVE_PORT "Port"
|
|
#define KEY_SLAVE_RUNNING_ID "RunningID"
|
|
#define KEY_SLAVE_SETTINGS "Settings"
|
|
#define KEY_SLAVE_BANDWIDTH "Bandwidth"
|
|
#define KEY_SLAVE_PASSWORD "Password"
|
|
|
|
const char *RegistryFileName = "slave.ini";
|
|
|
|
SlaveMasterClass SlaveMaster;
|
|
|
|
|
|
extern char DefaultRegistryModifier[1024];
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveServerClass::SlaveServerClass -- SlaveServerClass constuctor *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:51PM ST : Created *
|
|
*=============================================================================================*/
|
|
SlaveServerClass::SlaveServerClass(void)
|
|
{
|
|
Enable = false;
|
|
NickName[0] = 0;
|
|
Serial[0] = 0;
|
|
Port = 0;
|
|
Bandwidth = 0;
|
|
Password[0] = 0;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveServerClass::~SlaveServerClass -- SlaveServerClass desturctor *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:51PM ST : Created *
|
|
*=============================================================================================*/
|
|
SlaveServerClass::~SlaveServerClass(void)
|
|
{
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveServerClass::Set -- Set info about this slave *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Is slave enabled? *
|
|
* Nickname to use with this slave *
|
|
* Serial number to use with this slave *
|
|
* Port to use with this slave *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:50PM ST : Created *
|
|
*=============================================================================================*/
|
|
void SlaveServerClass::Set(bool enable, char *nick, char *serial, unsigned short port, char *settings_file, int bandwidth, char *password)
|
|
{
|
|
Enable = enable;
|
|
Port = port;
|
|
Bandwidth = bandwidth;
|
|
|
|
if (nick) {
|
|
strncpy(NickName, nick, sizeof(NickName));
|
|
}
|
|
|
|
if (serial) {
|
|
strncpy(Serial, serial, sizeof(Serial));
|
|
}
|
|
|
|
if (password) {
|
|
strncpy(Password, password, sizeof(Password));
|
|
}
|
|
|
|
if (settings_file) {
|
|
strncpy(SettingsFileName, settings_file, sizeof(SettingsFileName));
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveServerClass::Get -- Get info about this slave *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Is slave enabled? *
|
|
* Nickname to use with this slave *
|
|
* Serial number to use with this slave *
|
|
* Port to use with this slave *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:49PM ST : Created *
|
|
*=============================================================================================*/
|
|
void SlaveServerClass::Get(bool &enable, char *nick, char *serial, unsigned short &port, char *settings_file, int &bandwidth, char *password)
|
|
{
|
|
enable = Enable;
|
|
port = Port;
|
|
bandwidth = Bandwidth;
|
|
|
|
if (nick) {
|
|
strcpy(nick, NickName);
|
|
}
|
|
|
|
if (serial) {
|
|
strcpy(serial, Serial);
|
|
}
|
|
|
|
if (password) {
|
|
strcpy(password, Password);
|
|
}
|
|
|
|
if (settings_file) {
|
|
strcpy(settings_file, SettingsFileName);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::SlaveMasterClass -- SlaveMasterClass constructor *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:49PM ST : Created *
|
|
*=============================================================================================*/
|
|
SlaveMasterClass::SlaveMasterClass(void)
|
|
{
|
|
NumSlaveServers = 0;
|
|
SlaveMode = false;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::~SlaveMasterClass -- SlaveMasterClass destructor *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:49PM ST : Created *
|
|
*=============================================================================================*/
|
|
SlaveMasterClass::~SlaveMasterClass(void)
|
|
{
|
|
/*
|
|
** Make sure all slaves are gone before we quit.
|
|
*/
|
|
Wait_For_Slave_Shutdown();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Wait_For_Slave_Shutdown -- Wait for slaves to exit *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 1/7/2002 2:58PM ST : Created *
|
|
*=============================================================================================*/
|
|
void SlaveMasterClass::Wait_For_Slave_Shutdown(void)
|
|
{
|
|
if (!SlaveMode) {
|
|
|
|
unsigned long time = TIMEGETTIME();
|
|
int num_running = 0;
|
|
int last_num_running = 0;
|
|
bool forced = false;
|
|
|
|
/*
|
|
** Don't wait longer than 35 secs. It takes 15 secs for the slave to do it's intermission.
|
|
*/
|
|
while (TIMEGETTIME() - time < 40000) {
|
|
|
|
num_running = 0;
|
|
|
|
for (int i=0 ; i<NumSlaveServers ; i++) {
|
|
if (SlaveServers[i].IsRunning) {
|
|
if (SlaveServers[i].ProcessInfo.hProcess) {
|
|
unsigned long code;
|
|
int res = GetExitCodeProcess(SlaveServers[i].ProcessInfo.hProcess, &code);
|
|
|
|
if (res && code == STILL_ACTIVE) {
|
|
num_running++;
|
|
}
|
|
} else {
|
|
if (SlaveServers[i].ProcessInfo.dwProcessId) {
|
|
unsigned long ver = GetProcessVersion(SlaveServers[i].ProcessInfo.dwProcessId);
|
|
if (ver && ver == GetProcessVersion(GetCurrentProcessId())) {
|
|
num_running++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (num_running && num_running != last_num_running) {
|
|
ConsoleBox.Print("Waiting for %d slave(s) to shut down\n", num_running);
|
|
WWDEBUG_SAY(("Waiting for %d slave(s) to shut down\n", num_running));
|
|
last_num_running = num_running;
|
|
}
|
|
if (num_running == 0) {
|
|
break;
|
|
}
|
|
|
|
/*
|
|
** Force a shutdown if they are not cooperating.
|
|
*/
|
|
if (!forced && TIMEGETTIME() - time > 27000) {
|
|
forced = true;
|
|
for (int i=0 ; i<NumSlaveServers ; i++) {
|
|
if (SlaveServers[i].ProcessInfo.dwProcessId) {
|
|
unsigned long ver = GetProcessVersion(SlaveServers[i].ProcessInfo.dwProcessId);
|
|
if (ver && ver == GetProcessVersion(GetCurrentProcessId())) {
|
|
|
|
WWDEBUG_SAY(("Terminating process %d due to timeout\n", SlaveServers[i].ProcessInfo.dwProcessId));
|
|
|
|
/*
|
|
** Get a handle to the process.
|
|
*/
|
|
HANDLE proc_handle = OpenProcess(PROCESS_TERMINATE, false, SlaveServers[i].ProcessInfo.dwProcessId);
|
|
if (proc_handle != INVALID_HANDLE_VALUE) {
|
|
TerminateProcess(proc_handle, 0);
|
|
CloseHandle(proc_handle);
|
|
} else {
|
|
WWDEBUG_SAY(("Failed to get process handle for termination - error code %d\n", GetLastError()));
|
|
}
|
|
num_running++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Get_Slave -- Get slave server entry by index *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Index *
|
|
* *
|
|
* OUTPUT: Ptr to slave server *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:48PM ST : Created *
|
|
*=============================================================================================*/
|
|
SlaveServerClass *SlaveMasterClass::Get_Slave(int index)
|
|
{
|
|
WWASSERT(index < NumSlaveServers);
|
|
WWASSERT(index >= 0);
|
|
if (index >= 0 && index <NumSlaveServers) {
|
|
return(&SlaveServers[index]);
|
|
}
|
|
return(NULL);
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Save -- Save slave server info to registry *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:48PM ST : Created *
|
|
*=============================================================================================*/
|
|
void SlaveMasterClass::Save(void)
|
|
{
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_NET_SLAVE);
|
|
if (reg.Is_Valid()) {
|
|
reg.Set_Int(KEY_NUM_SLAVES, NumSlaveServers);
|
|
}
|
|
|
|
for (int i=0 ; i<NumSlaveServers ; i++) {
|
|
char entry[256];
|
|
sprintf(entry, "%s%d", KEY_SLAVE_NAME, i);
|
|
reg.Set_String(entry, SlaveServers[i].NickName);
|
|
|
|
sprintf(entry, "%s%d", KEY_SLAVE_PASSWORD, i);
|
|
reg.Set_String(entry, SlaveServers[i].Password);
|
|
|
|
sprintf(entry, "%s%d", KEY_SLAVE_SETTINGS, i);
|
|
reg.Set_String(entry, SlaveServers[i].SettingsFileName);
|
|
|
|
sprintf(entry, "%s%d", KEY_SLAVE_ENABLE, i);
|
|
reg.Set_Bool(entry, SlaveServers[i].Enable);
|
|
|
|
sprintf(entry, "%s%d", KEY_SLAVE_PORT, i);
|
|
reg.Set_Int(entry, SlaveServers[i].Port);
|
|
|
|
sprintf(entry, "%s%d", KEY_SLAVE_BANDWIDTH, i);
|
|
reg.Set_Int(entry, SlaveServers[i].Bandwidth);
|
|
|
|
sprintf(entry, "%s%d", KEY_SLAVE_SERIAL, i);
|
|
StringClass serial(SlaveServers[i].Serial, true);
|
|
StringClass encrypted_serial = serial;
|
|
if (serial.Get_Length()) {
|
|
ServerSettingsClass::Encrypt_Serial(serial, encrypted_serial);
|
|
}
|
|
reg.Set_String(entry, encrypted_serial.Peek_Buffer());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Load -- Fetch slave server info from registry *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:47PM ST : Created *
|
|
*=============================================================================================*/
|
|
void SlaveMasterClass::Load(void)
|
|
{
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_NET_SLAVE);
|
|
if (reg.Is_Valid()) {
|
|
NumSlaveServers = reg.Get_Int(KEY_NUM_SLAVES, 0);
|
|
}
|
|
|
|
char entry[256];
|
|
for (int i=0 ; i<NumSlaveServers ; i++) {
|
|
sprintf(entry, "%s%d", KEY_SLAVE_NAME, i);
|
|
reg.Get_String(entry, SlaveServers[i].NickName, sizeof(SlaveServers[i].NickName), "");
|
|
|
|
sprintf(entry, "%s%d", KEY_SLAVE_PASSWORD, i);
|
|
reg.Get_String(entry, SlaveServers[i].Password, sizeof(SlaveServers[i].Password), "");
|
|
|
|
sprintf(entry, "%s%d", KEY_SLAVE_ENABLE, i);
|
|
SlaveServers[i].Enable = reg.Get_Bool(entry, false);
|
|
|
|
sprintf(entry, "%s%d", KEY_SLAVE_PORT, i);
|
|
SlaveServers[i].Port = reg.Get_Int(entry, false);
|
|
|
|
sprintf(entry, "%s%d", KEY_SLAVE_BANDWIDTH, i);
|
|
SlaveServers[i].Bandwidth = reg.Get_Int(entry, false);
|
|
|
|
sprintf(entry, "%s%d", KEY_SLAVE_SETTINGS, i);
|
|
reg.Get_String(entry, SlaveServers[i].SettingsFileName, sizeof(SlaveServers[i].SettingsFileName), "");
|
|
|
|
sprintf(entry, "%s%d", KEY_SLAVE_SERIAL, i);
|
|
reg.Get_String(entry, SlaveServers[i].Serial, sizeof(SlaveServers[i].Serial), "");
|
|
if (strlen(SlaveServers[i].Serial)) {
|
|
StringClass serial(SlaveServers[i].Serial, true);
|
|
StringClass decrypted_serial = serial;
|
|
if (serial.Get_Length()) {
|
|
ServerSettingsClass::Decrypt_Serial(serial, decrypted_serial);
|
|
}
|
|
strcpy(SlaveServers[i].Serial, decrypted_serial.Peek_Buffer());
|
|
}
|
|
|
|
char filename[MAX_PATH];
|
|
sprintf(filename, "data\\%s", SlaveServers[i].SettingsFileName);
|
|
RawFileClass file(filename);
|
|
if (!file.Is_Available()) {
|
|
strcpy(SlaveServers[i].SettingsFileName, "svrcfg_cnc.ini");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Reset -- Clear out slave server list *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:47PM ST : Created *
|
|
*=============================================================================================*/
|
|
void SlaveMasterClass::Reset(void)
|
|
{
|
|
for (int i=0 ; i<NumSlaveServers ; i++) {
|
|
SlaveServers[i].Set(false, "", "", 0, "", 0, "");
|
|
}
|
|
NumSlaveServers = 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Add_Slave -- Add slave to list *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Is slave enabled? *
|
|
* Nickname for this slave to use *
|
|
* Serial number for this slave to use *
|
|
* Port number for this slave to use *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:46PM ST : Created *
|
|
*=============================================================================================*/
|
|
void SlaveMasterClass::Add_Slave(bool enable, char *nick, char *serial, unsigned short port, char *settings_file, int bandwidth, char *password)
|
|
{
|
|
WWASSERT(NumSlaveServers < MAX_SLAVES);
|
|
SlaveServers[NumSlaveServers++].Set(enable, nick, serial, port, settings_file, bandwidth, password);
|
|
}
|
|
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Aquire_Slave -- Find a running slave by it's process ID *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 12/16/2001 11:34PM ST : Created *
|
|
*=============================================================================================*/
|
|
bool SlaveMasterClass::Aquire_Slave(int index)
|
|
{
|
|
int proc_id = 0;
|
|
|
|
/*
|
|
** Try the slaves record of his process ID. If it's not there, it can't have run yet.
|
|
*/
|
|
char slave_name[64];
|
|
sprintf(slave_name, "\\slave_%d", index);
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass slave_reg(APPLICATION_SUB_KEY_NAME);
|
|
DefaultRegistryModifier[0] = 0;
|
|
if (slave_reg.Is_Valid()) {
|
|
proc_id = slave_reg.Get_Int("ProcessId", proc_id);
|
|
}
|
|
|
|
/*
|
|
** Try our record of the slaves process ID.
|
|
*/
|
|
if (proc_id == 0) {
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_NET_SLAVE);
|
|
if (reg.Is_Valid()) {
|
|
char entry[128];
|
|
sprintf(entry, "%s%d", KEY_SLAVE_RUNNING_ID, index);
|
|
proc_id = reg.Get_Int(entry, 0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Search for the slave's console window if we are in console mode. This is a better test than just hoping the Process ID is
|
|
** correct.
|
|
*/
|
|
if (ConsoleBox.Is_Exclusive()) {
|
|
HWND slave_window = ConsoleBox.Get_Slave_Window_By_Title(SlaveServers[index].NickName, SlaveServers[index].SettingsFileName);
|
|
if (slave_window != NULL) {
|
|
/*
|
|
** Note the process ID for later.
|
|
*/
|
|
SlaveServers[index].ProcessInfo.hProcess = NULL; // Don't know handle.
|
|
GetWindowThreadProcessId(slave_window, &SlaveServers[index].ProcessInfo.dwProcessId);
|
|
WWDEBUG_SAY(("Slave found by HWND with process ID %d\n", SlaveServers[index].ProcessInfo.dwProcessId));
|
|
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_NET_SLAVE);
|
|
if (reg.Is_Valid()) {
|
|
char entry[128];
|
|
sprintf(entry, "%s%d", KEY_SLAVE_RUNNING_ID, index);
|
|
reg.Set_Int(entry, SlaveServers[index].ProcessInfo.dwProcessId);
|
|
}
|
|
return(true);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** See if the process is already running.
|
|
*/
|
|
if (proc_id) {
|
|
|
|
unsigned long ver = GetProcessVersion(proc_id);
|
|
if (ver && ver == GetProcessVersion(GetCurrentProcessId())) {
|
|
|
|
/*
|
|
** It looks like one of our slaves. See if we already know about it.
|
|
*/
|
|
if (SlaveServers[index].IsRunning && SlaveServers[index].ProcessInfo.dwProcessId == (unsigned) proc_id) {
|
|
return(true);
|
|
}
|
|
|
|
/*
|
|
** Note the process ID for later.
|
|
*/
|
|
SlaveServers[index].ProcessInfo.hProcess = NULL; // Don't know handle.
|
|
SlaveServers[index].ProcessInfo.dwProcessId = proc_id;
|
|
WWDEBUG_SAY(("Slave found with process ID %d\n", SlaveServers[index].ProcessInfo.dwProcessId));
|
|
return(true);
|
|
}
|
|
}
|
|
|
|
return(false);
|
|
}
|
|
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Startup_Slaves -- Create extra slave server processes *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:46PM ST : Created *
|
|
*=============================================================================================*/
|
|
void SlaveMasterClass::Startup_Slaves(void)
|
|
{
|
|
if (!SlaveMode) {
|
|
|
|
/*
|
|
** Make sure we are a dedicated server.
|
|
*/
|
|
if (!The_Game() || The_Game()->IsDedicated.Is_True()) {
|
|
|
|
/*
|
|
** Slaves only available in windowed or console mode.
|
|
*/
|
|
if (WW3D::Is_Windowed() || ConsoleBox.Is_Exclusive()) {
|
|
|
|
/*
|
|
** Slaves only available in internet mode.
|
|
*/
|
|
GameModeClass *game_mode = GameModeManager::Find("WOL");
|
|
if (game_mode && game_mode->Is_Active()) {
|
|
|
|
Wait_For_Slave_Shutdown();
|
|
|
|
Load();
|
|
Delete_Registry_Copies();
|
|
Create_Registry_Copies();
|
|
|
|
/*
|
|
** Spawn the servers.
|
|
*/
|
|
char command_line[300];
|
|
for (int i=0 ; i<NumSlaveServers ; i++) {
|
|
if (SlaveServers[i].Enable) {
|
|
|
|
bool slave_running = false;
|
|
|
|
/*
|
|
** Get an access point into the slaves registry base.
|
|
*/
|
|
char slave_name[64];
|
|
sprintf(slave_name, "\\slave_%d", i);
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass slave_reg(APPLICATION_SUB_KEY_NAME);
|
|
DefaultRegistryModifier[0] = 0;
|
|
|
|
/*
|
|
** If we are autostarting then take inventory of which slaves are running already.
|
|
** An autostart after a crash should still see the slaves running. An autostart after a reboot will see no slaves.
|
|
*/
|
|
if (AutoRestart.Is_Active()) {
|
|
slave_running = Aquire_Slave(i);
|
|
}
|
|
|
|
/*
|
|
** Figure out the name of the .exe to run.
|
|
*/
|
|
char path_to_exe[256];
|
|
char drive[_MAX_DRIVE];
|
|
char dir[_MAX_DIR];
|
|
char path[_MAX_PATH];
|
|
GetModuleFileName(ProgramInstance, path_to_exe, sizeof(path_to_exe));
|
|
_splitpath(path_to_exe, drive, dir, NULL, NULL);
|
|
#ifdef FREEDEDICATEDSERVER
|
|
_makepath(path, drive, dir, "renegadeserver", "exe");
|
|
#else //FREEDEDICATEDSERVER
|
|
_makepath(path, drive, dir, "renegade", "exe");
|
|
#endif //FREEDEDICATEDSERVER
|
|
|
|
sprintf(command_line, "%s /MULTI /SLAVE /REGMOD=slave_%d", path, i);
|
|
if (ConsoleBox.Is_Exclusive()) {
|
|
strcat(command_line, " /NODX");
|
|
}
|
|
STARTUPINFO startup_info;
|
|
memset(&startup_info, 0, sizeof(startup_info));
|
|
startup_info.cb = sizeof(startup_info);
|
|
|
|
int result = 1;
|
|
if (!slave_running) {
|
|
|
|
if (slave_reg.Is_Valid()) {
|
|
slave_reg.Set_Int("ProcessId", 0);
|
|
}
|
|
result = CreateProcess(path, command_line, NULL, NULL, false, 0, NULL, NULL, &startup_info, &SlaveServers[i].ProcessInfo);
|
|
}
|
|
if (result) {
|
|
SlaveServers[i].IsRunning = true;
|
|
|
|
/*
|
|
** The process ID we have here is actually the ID of the slaves launcher. We need the ID of the actual
|
|
** game process. Wait a few seconds until the slave sets his ID into his registry location.
|
|
*/
|
|
if (!slave_running) {
|
|
unsigned long time = TIMEGETTIME();
|
|
while (TIMEGETTIME() - time < 10000) {
|
|
if (!slave_reg.Is_Valid()) {
|
|
break;
|
|
}
|
|
|
|
/*
|
|
** Break out once we read the slaves process ID from the registry indicating that the slave
|
|
** has already parsed its command line.
|
|
*/
|
|
int process_id = slave_reg.Get_Int("ProcessId", 0);
|
|
if (process_id != 0) {
|
|
SlaveServers[i].ProcessInfo.dwProcessId = process_id;
|
|
break;
|
|
}
|
|
Sleep(250);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Set a registry flag to say this server is active. We need to know this if the master server (us)
|
|
** crashes and restarts.
|
|
*/
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_NET_SLAVE);
|
|
if (reg.Is_Valid()) {
|
|
char entry[128];
|
|
sprintf(entry, "%s%d", KEY_SLAVE_RUNNING_ID, i);
|
|
reg.Set_Int(entry, SlaveServers[i].ProcessInfo.dwProcessId);
|
|
}
|
|
|
|
} else {
|
|
WWDEBUG_SAY(("Failed to start slave process - error code %d\n", GetLastError()));
|
|
SlaveServers[i].IsRunning = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
GameSideServerControlClass::Set_Welcome_Message();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Shutdown_Slaves -- Send quit message to all slaves *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:45PM ST : Created *
|
|
*=============================================================================================*/
|
|
void SlaveMasterClass::Shutdown_Slaves(void)
|
|
{
|
|
if (!SlaveMode) {
|
|
char password[64] = DEFAULT_SERVER_CONTROL_PASSWORD;
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_NET_SERVER_CONTROL);
|
|
reg.Get_String(SERVER_CONTROL_PASSWORD_KEY, password, sizeof(password), password);
|
|
|
|
for (int i=0 ; i<NumSlaveServers ; i++) {
|
|
if (SlaveServers[i].IsRunning) {
|
|
|
|
/*
|
|
** In case the slave was restarted - we won't know it's new process ID.
|
|
*/
|
|
Aquire_Slave(i);
|
|
|
|
/*
|
|
** Set the slaves auto-restart flag to false or it will just start right up again.
|
|
*/
|
|
char slave_name[64];
|
|
sprintf(slave_name, "\\slave_%d", i);
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass slave_reg(APPLICATION_SUB_KEY_NAME_WOLSETTINGS);
|
|
DefaultRegistryModifier[0] = 0;
|
|
slave_reg.Set_Int(AutoRestartClass::REG_VALUE_AUTO_RESTART_FLAG, 0);
|
|
|
|
/*
|
|
** Send the password to the slave to authenticate the connection.
|
|
*/
|
|
GameSideServerControlClass::Send_Message(password, ntohl(INADDR_LOOPBACK), SlaveServers[i].ControlPort);
|
|
Sleep(10);
|
|
GameSideServerControlClass::Send_Message("quit", ntohl(INADDR_LOOPBACK), SlaveServers[i].ControlPort);
|
|
|
|
/*
|
|
** Remember that we shut this guy down.
|
|
*/
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_NET_SLAVE);
|
|
if (reg.Is_Valid()) {
|
|
char entry[128];
|
|
sprintf(entry, "%s%d", KEY_SLAVE_RUNNING_ID, i);
|
|
reg.Set_Int(entry, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Shutdown_Slaves -- Send quit message to all slaves *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Slave login name *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:45PM ST : Created *
|
|
*=============================================================================================*/
|
|
bool SlaveMasterClass::Shutdown_Slave(char *slave_login)
|
|
{
|
|
if (!SlaveMode && slave_login) {
|
|
char password[64] = DEFAULT_SERVER_CONTROL_PASSWORD;
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_NET_SERVER_CONTROL);
|
|
reg.Get_String(SERVER_CONTROL_PASSWORD_KEY, password, sizeof(password), password);
|
|
|
|
for (int i=0 ; i<NumSlaveServers ; i++) {
|
|
if (SlaveServers[i].IsRunning && stricmp(slave_login, SlaveServers[i].NickName) == 0) {
|
|
|
|
/*
|
|
** In case the slave was restarted - we won't know it's new process ID.
|
|
*/
|
|
Aquire_Slave(i);
|
|
|
|
/*
|
|
** Set the slaves auto-restart flag to false or it will just start right up again.
|
|
*/
|
|
char slave_name[64];
|
|
sprintf(slave_name, "\\slave_%d", i);
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass slave_reg(APPLICATION_SUB_KEY_NAME_WOLSETTINGS);
|
|
DefaultRegistryModifier[0] = 0;
|
|
slave_reg.Set_Int(AutoRestartClass::REG_VALUE_AUTO_RESTART_FLAG, 0);
|
|
|
|
/*
|
|
** Send the password to the slave to authenticate the connection.
|
|
*/
|
|
GameSideServerControlClass::Send_Message(password, ntohl(INADDR_LOOPBACK), SlaveServers[i].ControlPort);
|
|
Sleep(10);
|
|
GameSideServerControlClass::Send_Message("quit", ntohl(INADDR_LOOPBACK), SlaveServers[i].ControlPort);
|
|
|
|
/*
|
|
** Remember that we shut this guy down.
|
|
*/
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_NET_SLAVE);
|
|
if (reg.Is_Valid()) {
|
|
char entry[128];
|
|
sprintf(entry, "%s%d", KEY_SLAVE_RUNNING_ID, i);
|
|
reg.Set_Int(entry, 0);
|
|
}
|
|
SlaveServers[i].IsRunning = false;
|
|
return(true);
|
|
}
|
|
}
|
|
}
|
|
return(false);
|
|
}
|
|
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Get_Slave_Info -- Get text slave info *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Ptr to text buffer *
|
|
* buffer size *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:45PM ST : Created *
|
|
*=============================================================================================*/
|
|
char *SlaveMasterClass::Get_Slave_Info(char *buffer, int buflen)
|
|
{
|
|
bool any = false;
|
|
if (buffer) {
|
|
assert(buflen >= 500);
|
|
*buffer = 0;
|
|
|
|
for (int i=0 ; i<NumSlaveServers ; i++) {
|
|
if (SlaveServers[i].IsRunning) {
|
|
any = true;
|
|
char temp[64];
|
|
sprintf(temp, " Slave %d on port %d\n", i+1, SlaveServers[i].ControlPort);
|
|
if (strlen(temp) + strlen(buffer) < (unsigned)buflen) {
|
|
strcat(buffer, temp);
|
|
}
|
|
}
|
|
}
|
|
if (!any) {
|
|
strcpy(buffer, "No slave servers active\n");
|
|
}
|
|
}
|
|
return(buffer);
|
|
}
|
|
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Create_Registry_Copies -- Create 'shadow' registry copies for slaves *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:44PM ST : Created *
|
|
*=============================================================================================*/
|
|
void SlaveMasterClass::Create_Registry_Copies(void)
|
|
{
|
|
WWASSERT(!SlaveMode);
|
|
|
|
/*
|
|
** Make sure the Process ID isn't set in our base registry. It's shouldn't be unless I ran with the /slave command during dev.
|
|
*/
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME);
|
|
if (reg.Is_Valid()) {
|
|
reg.Delete_Value("ProcessId");
|
|
}
|
|
|
|
RegistryClass::Save_Registry(RegistryFileName, APPLICATION_SUB_KEY_NAME);
|
|
|
|
char new_path[1024];
|
|
char slave_name[64];
|
|
|
|
for (int i=0 ; i<NumSlaveServers ; i++) {
|
|
if (SlaveServers[i].Enable) {
|
|
strcpy(new_path, APPLICATION_SUB_KEY_NAME);
|
|
sprintf(slave_name, "\\slave_%d", i);
|
|
strcat(new_path, slave_name);
|
|
RegistryClass::Load_Registry(RegistryFileName, APPLICATION_SUB_KEY_NAME, new_path);
|
|
|
|
/*
|
|
** Store the slave settings into the registry.
|
|
*/
|
|
|
|
/*
|
|
** Port numbers.
|
|
*/
|
|
{
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_NET_FIREWALL);
|
|
DefaultRegistryModifier[0] = 0;
|
|
RegistryClass my_reg(APPLICATION_SUB_KEY_NAME_NET_FIREWALL);
|
|
|
|
if (SlaveServers[i].Port != 0) {
|
|
reg.Set_Int("ForcePort", SlaveServers[i].Port);
|
|
} else {
|
|
reg.Set_Int("ForcePort", 0);
|
|
|
|
int port = my_reg.Get_Int("PortBase", PORT_BASE_MIN);
|
|
port = port + ((i+1) * 256);
|
|
if (port >= PORT_BASE_MAX-1) {
|
|
port -= (PORT_BASE_MAX - PORT_BASE_MIN);
|
|
}
|
|
reg.Set_Int("PortBase", port);
|
|
|
|
port = my_reg.Get_Int("PortPool", PORT_BASE_MIN);
|
|
port = port + ((i+1) * 1024);
|
|
if (port >= PORT_POOL_MAX-1) {
|
|
port -= (PORT_POOL_MAX - PORT_POOL_MIN);
|
|
}
|
|
reg.Set_Int("PortPool", port);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
** Server control info.
|
|
*/
|
|
{
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_NET_SERVER_CONTROL);
|
|
DefaultRegistryModifier[0] = 0;
|
|
RegistryClass my_reg(APPLICATION_SUB_KEY_NAME_NET_SERVER_CONTROL);
|
|
|
|
/*
|
|
** The password will be the same for all slaves but they each need a port to listen on.
|
|
*/
|
|
int my_sc_port = my_reg.Get_Int(SERVER_CONTROL_PORT_KEY, DEFAULT_SERVER_CONTROL_PORT);
|
|
int slave_port = my_sc_port;
|
|
if (my_sc_port == 0) {
|
|
/*
|
|
** If server control isn't enabled for me then we need to make up some port.
|
|
*/
|
|
slave_port = DEFAULT_SERVER_CONTROL_PORT;
|
|
}
|
|
slave_port += i;
|
|
slave_port++;
|
|
SlaveServers[i].ControlPort = slave_port;
|
|
reg.Set_Int(SERVER_CONTROL_PORT_KEY, slave_port);
|
|
|
|
/*
|
|
** Inherit this from the master now.
|
|
*/
|
|
//if (my_sc_port == 0) {
|
|
// reg.Set_Int(SERVER_CONTROL_LOOPBACK_KEY, 1);
|
|
//} else {
|
|
// reg.Set_Int(SERVER_CONTROL_LOOPBACK_KEY, 0);
|
|
//}
|
|
}
|
|
|
|
/*
|
|
** Login name.
|
|
*/
|
|
{
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_WOLSETTINGS);
|
|
DefaultRegistryModifier[0] = 0;
|
|
|
|
reg.Set_String("AutoLogin", SlaveServers[i].NickName);
|
|
reg.Set_String("LastLogin", SlaveServers[i].NickName);
|
|
}
|
|
|
|
/*
|
|
** Password name.
|
|
*/
|
|
{
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_WOLSETTINGS);
|
|
DefaultRegistryModifier[0] = 0;
|
|
|
|
reg.Set_String("AutoPassword", SlaveServers[i].Password);
|
|
}
|
|
|
|
|
|
/*
|
|
** Serial number.
|
|
*/
|
|
{
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME);
|
|
DefaultRegistryModifier[0] = 0;
|
|
|
|
StringClass serial(SlaveServers[i].Serial, true);
|
|
StringClass encrypted_serial = serial;
|
|
if (serial.Get_Length()) {
|
|
ServerSettingsClass::Encrypt_Serial(serial, encrypted_serial);
|
|
}
|
|
reg.Set_String(KEY_SLAVE_SERIAL, encrypted_serial.Peek_Buffer());
|
|
}
|
|
|
|
/*
|
|
** Make it autostart.
|
|
*/
|
|
{
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_WOLSETTINGS);
|
|
DefaultRegistryModifier[0] = 0;
|
|
|
|
if (reg.Is_Valid()) {
|
|
reg.Set_Int(AutoRestartClass::REG_VALUE_AUTO_RESTART_FLAG, 1);
|
|
|
|
int game_type = 0;
|
|
GameModeClass *game_mode = GameModeManager::Find("WOL");
|
|
if (game_mode && game_mode->Is_Active()) {
|
|
game_type = 1;
|
|
}
|
|
reg.Set_Int(AutoRestartClass::REG_VALUE_AUTO_RESTART_TYPE, game_type);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Tell it which multiplayer settings to use.
|
|
*/
|
|
{
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_OPTIONS);
|
|
DefaultRegistryModifier[0] = 0;
|
|
reg.Set_String("MultiplayerSettings", SlaveServers[i].SettingsFileName);
|
|
}
|
|
|
|
/*
|
|
** Set the SKU number to be the FDS SKU. Do this whether the Master is a FDS or not.
|
|
*/
|
|
{
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME);
|
|
DefaultRegistryModifier[0] = 0;
|
|
reg.Set_Int("SKU", RENEGADE_FDS_SKU);
|
|
}
|
|
|
|
/*
|
|
** Set the bandwidth information.
|
|
** A value of 0 means auto. A value of 0xffffffff means not specified (i.e. use master settings).
|
|
*/
|
|
{
|
|
int bw = SlaveServers[i].Bandwidth;
|
|
if (bw != -1) {
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass reg_netopt(APPLICATION_SUB_KEY_NAME_NETOPTIONS);
|
|
RegistryClass reg_bw(APPLICATION_SUB_KEY_NAME_BANDTEST);
|
|
DefaultRegistryModifier[0] = 0;
|
|
RegistryClass my_reg_netopt(APPLICATION_SUB_KEY_NAME_NETOPTIONS);
|
|
RegistryClass my_reg_bw(APPLICATION_SUB_KEY_NAME_BANDTEST);
|
|
|
|
//reg_netopt.Set_Int("BandwidthType", BANDWIDTH_AUTO);
|
|
cUserOptions::Set_Bandwidth_Type(BANDWIDTH_AUTO);
|
|
int slave_bw = bw;
|
|
|
|
/*
|
|
** If bandwidth is set to auto then divide it by the number of servers on this box.
|
|
*/
|
|
if (slave_bw == 0) {
|
|
slave_bw = my_reg_bw.Get_Int("Up", 0);
|
|
int num = Get_Num_Enabled_Slaves();
|
|
if (num) {
|
|
slave_bw = slave_bw / (num+1);
|
|
}
|
|
}
|
|
reg_bw.Set_Int("Up", slave_bw);
|
|
reg_bw.Set_Int("Down", slave_bw);
|
|
}
|
|
}
|
|
|
|
|
|
#if (0)
|
|
/*
|
|
** Give the window a different position so we are not completely overlapping.
|
|
*/
|
|
{
|
|
strcpy(DefaultRegistryModifier, slave_name+1);
|
|
RegistryClass reg(APPLICATION_SUB_KEY_NAME_OPTIONS);
|
|
DefaultRegistryModifier[0] = 0;
|
|
reg.Set_Int("WindowX", (i * 32) + 32);
|
|
reg.Set_Int("WindowY", (i * 32) + 32);
|
|
}
|
|
#endif //(0)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Delete_Registry_Copies -- Delete old slave registry copies *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Nothing *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 11/21/2001 3:44PM ST : Created *
|
|
*=============================================================================================*/
|
|
void SlaveMasterClass::Delete_Registry_Copies(void)
|
|
{
|
|
HKEY base_key;
|
|
long result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, APPLICATION_SUB_KEY_NAME, 0, KEY_ALL_ACCESS, &base_key);
|
|
WWASSERT(result == ERROR_SUCCESS);
|
|
|
|
if (result == ERROR_SUCCESS) {
|
|
int index = 0;
|
|
char new_path[1024];
|
|
char slave_name[64];
|
|
|
|
while (index < MAX_SLAVES) {
|
|
strcpy(new_path, APPLICATION_SUB_KEY_NAME);
|
|
sprintf(slave_name, "\\slave_%d", index);
|
|
strcat(new_path, slave_name);
|
|
RegistryClass::Delete_Registry_Tree(new_path);
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************************************
|
|
* SlaveMasterClass::Get_Num_Enabled_Slaves -- How many slaves are enabled? *
|
|
* *
|
|
* *
|
|
* *
|
|
* INPUT: Nothing *
|
|
* *
|
|
* OUTPUT: Number of enabled slaves *
|
|
* *
|
|
* WARNINGS: None *
|
|
* *
|
|
* HISTORY: *
|
|
* 1/5/2002 11:47PM ST : Created *
|
|
*=============================================================================================*/
|
|
int SlaveMasterClass::Get_Num_Enabled_Slaves(void)
|
|
{
|
|
int enabled = 0;
|
|
|
|
for (int i=0 ; i<NumSlaveServers ; i++) {
|
|
if (SlaveServers[i].Enable) {
|
|
enabled++;
|
|
}
|
|
}
|
|
return(enabled);
|
|
} |