Merge pull request #1845 from birkett/master
Enable MCServer to run as a service on Windows.
This commit is contained in:
commit
11f5ee27ab
4
MCServer/delete_windows_service.cmd
Normal file
4
MCServer/delete_windows_service.cmd
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
@echo off
|
||||||
|
set SERVICENAME="MCServer"
|
||||||
|
|
||||||
|
sc delete %SERVICENAME%
|
7
MCServer/install_windows_service.cmd
Normal file
7
MCServer/install_windows_service.cmd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
rem Alter this if you need to install multiple instances.
|
||||||
|
@echo off
|
||||||
|
set SERVICENAME="MCServer"
|
||||||
|
|
||||||
|
set CURRENTDIR=%CD%
|
||||||
|
sc create %SERVICENAME% binPath= "%CURRENTDIR%\MCServer.exe /service" start= auto DisplayName= %SERVICENAME%
|
||||||
|
sc description %SERVICENAME% "Minecraft server instance"
|
@ -26,8 +26,18 @@ class cBlockIDMap
|
|||||||
typedef std::map<AString, std::pair<short, short>, Comparator> ItemMap;
|
typedef std::map<AString, std::pair<short, short>, Comparator> ItemMap;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
static bool m_bHasRunInit;
|
||||||
|
|
||||||
cBlockIDMap(void)
|
cBlockIDMap(void)
|
||||||
{
|
{
|
||||||
|
// Dont load items.ini on construct, this will search the wrong path when running as a service.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
m_bHasRunInit = true;
|
||||||
|
|
||||||
cIniFile Ini;
|
cIniFile Ini;
|
||||||
if (!Ini.ReadFile("items.ini"))
|
if (!Ini.ReadFile("items.ini"))
|
||||||
{
|
{
|
||||||
@ -174,7 +184,7 @@ protected:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cBlockIDMap::m_bHasRunInit = false;
|
||||||
static cBlockIDMap gsBlockIDMap;
|
static cBlockIDMap gsBlockIDMap;
|
||||||
|
|
||||||
|
|
||||||
@ -209,6 +219,10 @@ int BlockStringToType(const AString & a_BlockTypeString)
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!gsBlockIDMap.m_bHasRunInit)
|
||||||
|
{
|
||||||
|
gsBlockIDMap.init();
|
||||||
|
}
|
||||||
return gsBlockIDMap.Resolve(TrimString(a_BlockTypeString));
|
return gsBlockIDMap.Resolve(TrimString(a_BlockTypeString));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +236,11 @@ bool StringToItem(const AString & a_ItemTypeString, cItem & a_Item)
|
|||||||
{
|
{
|
||||||
ItemName = ItemName.substr(10);
|
ItemName = ItemName.substr(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!gsBlockIDMap.m_bHasRunInit)
|
||||||
|
{
|
||||||
|
gsBlockIDMap.init();
|
||||||
|
}
|
||||||
return gsBlockIDMap.ResolveItem(ItemName, a_Item);
|
return gsBlockIDMap.ResolveItem(ItemName, a_Item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,6 +250,10 @@ bool StringToItem(const AString & a_ItemTypeString, cItem & a_Item)
|
|||||||
|
|
||||||
AString ItemToString(const cItem & a_Item)
|
AString ItemToString(const cItem & a_Item)
|
||||||
{
|
{
|
||||||
|
if (!gsBlockIDMap.m_bHasRunInit)
|
||||||
|
{
|
||||||
|
gsBlockIDMap.init();
|
||||||
|
}
|
||||||
return gsBlockIDMap.Desolve(a_Item.m_ItemType, a_Item.m_ItemDamage);
|
return gsBlockIDMap.Desolve(a_Item.m_ItemType, a_Item.m_ItemDamage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,6 +263,10 @@ AString ItemToString(const cItem & a_Item)
|
|||||||
|
|
||||||
AString ItemTypeToString(short a_ItemType)
|
AString ItemTypeToString(short a_ItemType)
|
||||||
{
|
{
|
||||||
|
if (!gsBlockIDMap.m_bHasRunInit)
|
||||||
|
{
|
||||||
|
gsBlockIDMap.init();
|
||||||
|
}
|
||||||
return gsBlockIDMap.Desolve(a_ItemType, -1);
|
return gsBlockIDMap.Desolve(a_ItemType, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
src/Root.cpp
10
src/Root.cpp
@ -84,9 +84,12 @@ void cRoot::InputThread(cRoot & a_Params)
|
|||||||
if (m_TerminateEventRaised || !std::cin.good())
|
if (m_TerminateEventRaised || !std::cin.good())
|
||||||
{
|
{
|
||||||
// We have come here because the std::cin has received an EOF / a terminate signal has been sent, and the server is still running; stop the server:
|
// We have come here because the std::cin has received an EOF / a terminate signal has been sent, and the server is still running; stop the server:
|
||||||
|
if (m_RunAsService) // HACK: Dont kill if running as a service
|
||||||
|
{
|
||||||
a_Params.m_bStop = true;
|
a_Params.m_bStop = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -268,6 +271,13 @@ void cRoot::Start(void)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cRoot::SetStopping(bool a_Stopping)
|
||||||
|
{
|
||||||
|
m_bStop = a_Stopping;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void cRoot::LoadGlobalSettings()
|
void cRoot::LoadGlobalSettings()
|
||||||
{
|
{
|
||||||
|
@ -46,6 +46,7 @@ public:
|
|||||||
// tolua_end
|
// tolua_end
|
||||||
|
|
||||||
static bool m_TerminateEventRaised;
|
static bool m_TerminateEventRaised;
|
||||||
|
static bool m_RunAsService;
|
||||||
|
|
||||||
|
|
||||||
cRoot(void);
|
cRoot(void);
|
||||||
@ -53,6 +54,9 @@ public:
|
|||||||
|
|
||||||
void Start(void);
|
void Start(void);
|
||||||
|
|
||||||
|
// Added so the service handler can request a stop
|
||||||
|
void SetStopping(bool a_Stopping);
|
||||||
|
|
||||||
// tolua_begin
|
// tolua_begin
|
||||||
cServer * GetServer(void) { return m_Server; }
|
cServer * GetServer(void) { return m_Server; }
|
||||||
cWorld * GetDefaultWorld(void);
|
cWorld * GetDefaultWorld(void);
|
||||||
|
222
src/main.cpp
222
src/main.cpp
@ -15,6 +15,8 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** Make the Root instance global, so it can be terminated from the worker threads */
|
||||||
|
cRoot Root;
|
||||||
|
|
||||||
|
|
||||||
/** If something has told the server to stop; checked periodically in cRoot */
|
/** If something has told the server to stop; checked periodically in cRoot */
|
||||||
@ -29,8 +31,15 @@ bool g_ShouldLogCommIn;
|
|||||||
/** If set to true, the protocols will log each player's outgoing (S->C) communication to a per-connection logfile */
|
/** If set to true, the protocols will log each player's outgoing (S->C) communication to a per-connection logfile */
|
||||||
bool g_ShouldLogCommOut;
|
bool g_ShouldLogCommOut;
|
||||||
|
|
||||||
|
/** If set to true, binary will attempt to run as a service on Windows */
|
||||||
|
bool cRoot::m_RunAsService = false;
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
|
||||||
|
HANDLE g_ServiceThread = INVALID_HANDLE_VALUE;
|
||||||
|
#define SERVICE_NAME "MCServerService"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/// If defined, a thorough leak finder will be used (debug MSVC only); leaks will be output to the Output window
|
/// If defined, a thorough leak finder will be used (debug MSVC only); leaks will be output to the Output window
|
||||||
@ -179,6 +188,165 @@ BOOL CtrlHandler(DWORD fdwCtrlType)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// universalMain - Main startup logic for both standard running and as a service
|
||||||
|
|
||||||
|
void universalMain()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE))
|
||||||
|
{
|
||||||
|
LOGERROR("Could not install the Windows CTRL handler!");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Initialize logging subsystem:
|
||||||
|
cLogger::InitiateMultithreading();
|
||||||
|
|
||||||
|
// Initialize LibEvent:
|
||||||
|
cNetworkSingleton::Get();
|
||||||
|
|
||||||
|
#if !defined(ANDROID_NDK)
|
||||||
|
try
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
Root.Start();
|
||||||
|
}
|
||||||
|
#if !defined(ANDROID_NDK)
|
||||||
|
catch (std::exception & e)
|
||||||
|
{
|
||||||
|
LOGERROR("Standard exception: %s", e.what());
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
LOGERROR("Unknown exception!");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
g_ServerTerminated = true;
|
||||||
|
|
||||||
|
// Shutdown all of LibEvent:
|
||||||
|
cNetworkSingleton::Get().Terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// serviceWorkerThread: Keep the service alive
|
||||||
|
|
||||||
|
DWORD WINAPI serviceWorkerThread(LPVOID lpParam)
|
||||||
|
{
|
||||||
|
UNREFERENCED_PARAMETER(lpParam);
|
||||||
|
|
||||||
|
// Do the normal startup
|
||||||
|
universalMain();
|
||||||
|
|
||||||
|
return ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// serviceSetState: Set the internal status of the service
|
||||||
|
|
||||||
|
void serviceSetState(DWORD acceptedControls, DWORD newState, DWORD exitCode)
|
||||||
|
{
|
||||||
|
SERVICE_STATUS serviceStatus;
|
||||||
|
ZeroMemory(&serviceStatus, sizeof(SERVICE_STATUS));
|
||||||
|
serviceStatus.dwCheckPoint = 0;
|
||||||
|
serviceStatus.dwControlsAccepted = acceptedControls;
|
||||||
|
serviceStatus.dwCurrentState = newState;
|
||||||
|
serviceStatus.dwServiceSpecificExitCode = 0;
|
||||||
|
serviceStatus.dwServiceType = SERVICE_WIN32;
|
||||||
|
serviceStatus.dwWaitHint = 0;
|
||||||
|
serviceStatus.dwWin32ExitCode = exitCode;
|
||||||
|
|
||||||
|
if (SetServiceStatus(g_StatusHandle, &serviceStatus) == FALSE)
|
||||||
|
{
|
||||||
|
LOGERROR("SetServiceStatus() failed\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// serviceCtrlHandler: Handle stop events from the Service Control Manager
|
||||||
|
|
||||||
|
void WINAPI serviceCtrlHandler(DWORD CtrlCode)
|
||||||
|
{
|
||||||
|
switch (CtrlCode)
|
||||||
|
{
|
||||||
|
case SERVICE_CONTROL_STOP:
|
||||||
|
{
|
||||||
|
Root.SetStopping(true);
|
||||||
|
serviceSetState(0, SERVICE_STOP_PENDING, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// serviceMain: Startup logic for running as a service
|
||||||
|
|
||||||
|
void WINAPI serviceMain(DWORD argc, TCHAR *argv[])
|
||||||
|
{
|
||||||
|
#if defined(_DEBUG) && defined(DEBUG_SERVICE_STARTUP)
|
||||||
|
Sleep(10000);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
char applicationFilename[MAX_PATH];
|
||||||
|
char applicationDirectory[MAX_PATH];
|
||||||
|
|
||||||
|
GetModuleFileName(NULL, applicationFilename, MAX_PATH); // This binaries fill path.
|
||||||
|
|
||||||
|
// GetModuleFileName() returns the path and filename. Strip off the filename.
|
||||||
|
strncpy(applicationDirectory, applicationFilename, (strrchr(applicationFilename, '\\') - applicationFilename));
|
||||||
|
applicationDirectory[strlen(applicationDirectory)] = '\0'; // Make sure new path is null terminated
|
||||||
|
|
||||||
|
// Services are run by the SCM, and inherit its working directory - usually System32.
|
||||||
|
// Set the working directory to the same location as the binary.
|
||||||
|
SetCurrentDirectory(applicationDirectory);
|
||||||
|
|
||||||
|
g_StatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, serviceCtrlHandler);
|
||||||
|
|
||||||
|
if (g_StatusHandle == NULL)
|
||||||
|
{
|
||||||
|
OutputDebugString("RegisterServiceCtrlHandler() failed\n");
|
||||||
|
serviceSetState(0, SERVICE_STOPPED, GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceSetState(SERVICE_ACCEPT_STOP, SERVICE_RUNNING, 0);
|
||||||
|
|
||||||
|
g_ServiceThread = CreateThread(NULL, 0, serviceWorkerThread, NULL, 0, NULL);
|
||||||
|
if (g_ServiceThread == NULL)
|
||||||
|
{
|
||||||
|
OutputDebugString("CreateThread() failed\n");
|
||||||
|
serviceSetState(0, SERVICE_STOPPED, GetLastError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WaitForSingleObject(g_ServiceThread, INFINITE); // Wait here for a stop signal.
|
||||||
|
|
||||||
|
CloseHandle(g_ServiceThread);
|
||||||
|
|
||||||
|
serviceSetState(0, SERVICE_STOPPED, 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// main:
|
// main:
|
||||||
@ -219,13 +387,6 @@ int main( int argc, char **argv)
|
|||||||
#endif // _WIN32 && !_WIN64
|
#endif // _WIN32 && !_WIN64
|
||||||
// End of dump-file magic
|
// End of dump-file magic
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE))
|
|
||||||
{
|
|
||||||
LOGERROR("Could not install the Windows CTRL handler!");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(_DEBUG) && defined(_MSC_VER)
|
#if defined(_DEBUG) && defined(_MSC_VER)
|
||||||
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
|
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
|
||||||
|
|
||||||
@ -280,42 +441,39 @@ int main( int argc, char **argv)
|
|||||||
{
|
{
|
||||||
setvbuf(stdout, nullptr, _IONBF, 0);
|
setvbuf(stdout, nullptr, _IONBF, 0);
|
||||||
}
|
}
|
||||||
|
else if (NoCaseCompare(Arg, "/service") == 0)
|
||||||
|
{
|
||||||
|
cRoot::m_RunAsService = true;
|
||||||
|
}
|
||||||
} // for i - argv[]
|
} // for i - argv[]
|
||||||
|
|
||||||
// Initialize logging subsystem:
|
#if defined(_WIN32)
|
||||||
cLogger::InitiateMultithreading();
|
// Attempt to run as a service
|
||||||
|
if (cRoot::m_RunAsService)
|
||||||
|
{
|
||||||
|
SERVICE_TABLE_ENTRY ServiceTable[] =
|
||||||
|
{
|
||||||
|
{ SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)serviceMain },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize LibEvent:
|
if (StartServiceCtrlDispatcher(ServiceTable) == FALSE)
|
||||||
cNetworkSingleton::Get();
|
{
|
||||||
|
LOGERROR("Attempted, but failed, service startup.");
|
||||||
#if !defined(ANDROID_NDK)
|
return GetLastError();
|
||||||
try
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
cRoot Root;
|
// Not running as a service, do normal startup
|
||||||
Root.Start();
|
universalMain();
|
||||||
}
|
}
|
||||||
#if !defined(ANDROID_NDK)
|
|
||||||
catch (std::exception & e)
|
|
||||||
{
|
|
||||||
LOGERROR("Standard exception: %s", e.what());
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
LOGERROR("Unknown exception!");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER)
|
#if defined(_MSC_VER) && defined(_DEBUG) && defined(ENABLE_LEAK_FINDER)
|
||||||
DeinitLeakFinder();
|
DeinitLeakFinder();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
g_ServerTerminated = true;
|
|
||||||
|
|
||||||
// Shutdown all of LibEvent:
|
|
||||||
cNetworkSingleton::Get().Terminate();
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user