Cuberite
A lightweight, fast and extensible game server for Minecraft
Root.cpp
Go to the documentation of this file.
1 
2 #include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
3 
4 #include "Root.h"
5 #include "main.h"
6 
7 // STD lib hreaders:
8 #include <iostream>
9 
10 // OS-specific headers:
11 #if defined(_WIN32)
12  #include <psapi.h>
13 #elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
14  #include <signal.h>
15  #if defined(__linux__)
16  #include <fstream>
17 
18  #if !defined(__GLIBC__)
19  #include <sys/select.h>
20  #endif
21  #elif defined(__APPLE__)
22  #include <mach/mach.h>
23  #elif defined(__FreeBSD__)
24  #include <kvm.h>
25  #include <fcntl.h>
26  #include <sys/sysctl.h>
27  #include <sys/user.h>
28  #endif
29 #endif
30 
31 #include "Server.h"
32 #include "World.h"
33 #include "WebAdmin.h"
34 #include "BrewingRecipes.h"
35 #include "FurnaceRecipe.h"
36 #include "CraftingRecipes.h"
37 #include "Protocol/RecipeMapper.h"
38 #include "Bindings/PluginManager.h"
39 #include "MonsterConfig.h"
40 #include "Entities/Player.h"
41 #include "Blocks/BlockHandler.h"
42 #include "Items/ItemHandler.h"
43 #include "Chunk.h"
44 #include "Protocol/ProtocolRecognizer.h" // for protocol version constants
45 #include "CommandOutput.h"
46 #include "DeadlockDetect.h"
47 #include "LoggerListeners.h"
48 #include "BuildInfo.h"
49 #include "IniFile.h"
51 #include "Logger.h"
52 #include "ClientHandle.h"
53 
54 
55 
56 
57 
58 #ifdef __clang__
59  #pragma clang diagnostic push
60  #pragma clang diagnostic ignored "-Wglobal-constructors"
61 #endif
62 
66 
67 #ifdef __clang__
68  #pragma clang diagnostic pop
69 #endif
70 
71 
72 
73 
74 
75 cRoot::cRoot(void) :
76  m_pDefaultWorld(nullptr),
77  m_Server(nullptr),
78  m_MonsterConfig(nullptr),
79  m_CraftingRecipes(nullptr),
80  m_FurnaceRecipe(nullptr),
81  m_BrewingRecipes(nullptr),
82  m_WebAdmin(nullptr),
83  m_PluginManager(nullptr),
84  m_MojangAPI(nullptr)
85 {
86  s_Root = this;
88 }
89 
90 
91 
92 
93 
95 {
96  s_Root = nullptr;
97 }
98 
99 
100 
101 
102 
104 {
105  auto consoleLogListener = MakeConsoleListener(g_RunAsService);
106  auto consoleAttachment = cLogger::GetInstance().AttachListener(std::move(consoleLogListener));
107 
108  cLogger::cAttachment fileAttachment;
109  if (!a_OverridesRepo.HasValue("Server","DisableLogFile"))
110  {
111  auto fileLogListenerRet = MakeFileListener();
112  if (!fileLogListenerRet.first)
113  {
114  throw std::runtime_error("failed to open log file");
115  }
116  fileAttachment = cLogger::GetInstance().AttachListener(std::move(fileLogListenerRet.second));
117  }
118 
119  LOG("--- Started Log ---");
120 
121 #ifdef BUILD_ID
122  LOG("Cuberite " BUILD_SERIES_NAME " (id: " BUILD_ID ")");
123  LOG("from commit " BUILD_COMMIT_ID " built at: " BUILD_DATETIME);
124 #endif
125 
126  cDeadlockDetect dd;
127  auto BeginTime = std::chrono::steady_clock::now();
128 
130 
131  LOG("Creating new server instance...");
132  m_Server = new cServer();
133 
134  LOG("Reading server config...");
135 
136  m_SettingsFilename = "settings.ini";
137  if (a_OverridesRepo.HasValue("Server","ConfigFile"))
138  {
139  m_SettingsFilename = a_OverridesRepo.GetValue("Server","ConfigFile");
140  }
141 
142  auto IniFile = std::make_unique<cIniFile>();
143  bool IsNewIniFile = !IniFile->ReadFile(m_SettingsFilename);
144 
145  if (IsNewIniFile)
146  {
147  LOGWARN("Regenerating settings.ini, all settings will be reset");
148  IniFile->AddHeaderComment(" This is the main server configuration");
149  IniFile->AddHeaderComment(" Most of the settings here can be configured using the webadmin interface, if enabled in webadmin.ini");
150  }
151 
152  auto settingsRepo = std::make_unique<cOverridesSettingsRepository>(std::move(IniFile), a_OverridesRepo);
153 
154  LOG("Starting server...");
155 
156  // cClientHandle::FASTBREAK_PERCENTAGE = settingsRepo->GetValueSetI("AntiCheat", "FastBreakPercentage", 97) / 100.0f;
157  cClientHandle::FASTBREAK_PERCENTAGE = 0; // AntiCheat disabled due to bugs. We will enabled it once they are fixed. See #3506.
158 
159  m_MojangAPI = new cMojangAPI;
160  bool ShouldAuthenticate = settingsRepo->GetValueSetB("Authentication", "Authenticate", true);
161  m_MojangAPI->Start(*settingsRepo, ShouldAuthenticate); // Mojang API needs to be started before plugins, so that plugins may use it for DB upgrades on server init
162  if (!m_Server->InitServer(*settingsRepo, ShouldAuthenticate))
163  {
164  settingsRepo->Flush();
165  throw std::runtime_error("failure starting server");
166  }
167 
168  m_WebAdmin = new cWebAdmin();
169  m_WebAdmin->Init();
170 
171  LOGD("Loading settings...");
172  m_RankManager.reset(new cRankManager());
173  m_RankManager->Initialize(*m_MojangAPI);
175  m_RecipeMapper.reset(new cRecipeMapper());
177  m_BrewingRecipes.reset(new cBrewingRecipes());
178 
179  LOGD("Loading worlds...");
180  LoadWorlds(dd, *settingsRepo, IsNewIniFile);
181 
182  LOGD("Loading plugin manager...");
184  m_PluginManager->ReloadPluginsNow(*settingsRepo);
185 
186  LOGD("Loading MonsterConfig...");
188 
189  // This sets stuff in motion
190  LOGD("Starting Authenticator...");
191  m_Authenticator.Start(*settingsRepo);
192 
193  LOGD("Starting worlds...");
194  StartWorlds(dd);
195 
196  if (settingsRepo->GetValueSetB("DeadlockDetect", "Enabled", true))
197  {
198  LOGD("Starting deadlock detector...");
199  dd.Start(settingsRepo->GetValueSetI("DeadlockDetect", "IntervalSec", 20));
200  }
201 
202  settingsRepo->Flush();
203 
204  LOGD("Finalising startup...");
205  if (m_Server->Start())
206  {
207  m_WebAdmin->Start();
208 
209  LOG("Startup complete, took %ldms!", static_cast<long int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count()));
210 
211  // Save the current time
212  m_StartTime = std::chrono::steady_clock::now();
213 
214  HandleInput();
215  s_StopEvent.Wait();
216 
217  // Stop the server:
218  m_WebAdmin->Stop();
219 
220  LOG("Shutting down server...");
221  m_Server->Shutdown();
222  } // if (m_Server->Start()
223 
224  delete m_MojangAPI; m_MojangAPI = nullptr;
225 
226  LOGD("Shutting down deadlock detector...");
227  dd.Stop();
228 
229  LOGD("Stopping world threads...");
230  StopWorlds(dd);
231 
232  LOGD("Stopping authenticator...");
234 
235  LOGD("Freeing MonsterConfig...");
236  delete m_MonsterConfig; m_MonsterConfig = nullptr;
237  delete m_WebAdmin; m_WebAdmin = nullptr;
238 
239  LOGD("Unloading recipes...");
240  delete m_FurnaceRecipe; m_FurnaceRecipe = nullptr;
241  delete m_CraftingRecipes; m_CraftingRecipes = nullptr;
242 
243  LOGD("Stopping plugin manager...");
244  delete m_PluginManager; m_PluginManager = nullptr;
245 
246  LOG("Cleaning up...");
247  delete m_Server; m_Server = nullptr;
248 
249  LOG("Shutdown successful!");
250  LOG("--- Stopped Log ---");
251 
253 }
254 
255 
256 
257 
258 
260 {
262 }
263 
264 
265 
266 
267 
269 {
271 }
272 
273 
274 
275 
276 
278 {
279  // Nothing needed yet
280 }
281 
282 
283 
284 
285 
286 void cRoot::LoadWorlds(cDeadlockDetect & a_dd, cSettingsRepositoryInterface & a_Settings, bool a_IsNewIniFile)
287 {
288  if (a_IsNewIniFile)
289  {
290  a_Settings.AddValue("Worlds", "DefaultWorld", "world");
291  a_Settings.AddValue("Worlds", "World", "world_nether");
292  a_Settings.AddValue("Worlds", "World", "world_the_end");
293  a_Settings.AddValue("WorldPaths", "world", "world");
294  a_Settings.AddValue("WorldPaths", "world_nether", "world_nether");
295  a_Settings.AddValue("WorldPaths", "world_the_end", "world_the_end");
296 
297  const AStringVector WorldNames{ "world", "world_nether", "world_the_end" };
298  m_pDefaultWorld = &m_WorldsByName.try_emplace("world", "world", "world", a_dd, WorldNames).first->second;
299  m_WorldsByName.try_emplace("world_nether", "world_nether", "world_nether", a_dd, WorldNames, dimNether, "world");
300  m_WorldsByName.try_emplace("world_the_end", "world_the_end", "world_the_end", a_dd, WorldNames, dimEnd, "world");
301  return;
302  }
303 
304  // Build a list of all world names
305  auto Worlds = a_Settings.GetValues("Worlds");
306  AStringVector WorldNames(Worlds.size());
307  for (const auto & World : Worlds)
308  {
309  WorldNames.push_back(World.second);
310  }
311 
312  // Get the default world
313  AString DefaultWorldName = a_Settings.GetValueSet("Worlds", "DefaultWorld", "world");
314  AString DefaultWorldPath = a_Settings.GetValueSet("WorldPaths", DefaultWorldName, DefaultWorldName);
315  m_pDefaultWorld = &m_WorldsByName.try_emplace(DefaultWorldName, DefaultWorldName, DefaultWorldPath, a_dd, WorldNames).first->second;
316 
317  // Then load the other worlds
318  if (Worlds.size() <= 0)
319  {
320  return;
321  }
322 
323  /* Here are the world creation rules. Note that these only apply for a world which is in settings.ini but has no world.ini file.
324  If an ini file is present, it overrides the world linkages and the dimension type in cWorld::start()
325  The creation rules are as follows:
326 
327  - If a world exists in settings.ini but has no world.ini, then:
328  - If the world name is x_nether, create a world.ini with the dimension type "nether".
329  - If a world called x exists, set it as x_nether's overworld.
330  - Otherwise set the default world as x_nether's overworld.
331 
332  - If the world name is x_the_end or x_end, create a world.ini with the dimension type "end".
333  - If a world called x exists, set it as x_the_end's overworld.
334  - Otherwise set the default world as x_the_end's overworld.
335 
336  - If the world name is x (and doesn't end with _the_end, _end or _nether)
337  - Create a world.ini with a dimension type of "overworld".
338  - If a world called x_nether exists, set it as x's nether world.
339  - Otherwise set x's nether world to blank.h
340  - If a world called x_the_end or x_end exists, set it as x's end world.
341  - Otherwise set x's nether world to blank.
342 
343  */
344 
345  bool FoundAdditionalWorlds = false;
346  for (const auto & WorldNameValue : Worlds)
347  {
348  AString ValueName = WorldNameValue.first;
349  if (ValueName.compare("World") != 0)
350  {
351  continue;
352  }
353  AString WorldName = WorldNameValue.second;
354  if (WorldName.empty())
355  {
356  continue;
357  }
358  FoundAdditionalWorlds = true;
359  AString LowercaseName = StrToLower(WorldName);
360  AString WorldPath = a_Settings.GetValueSet("WorldPaths", WorldName, WorldName);
361  AString NetherAppend = "_nether";
362  AString EndAppend1 = "_the_end";
363  AString EndAppend2 = "_end";
364 
365  // The default world is an overworld with no links
366  eDimension Dimension = dimOverworld;
367  AString LinkTo;
368 
369  // if the world is called x_nether
370  if ((LowercaseName.size() > NetherAppend.size()) && (LowercaseName.substr(LowercaseName.size() - NetherAppend.size()) == NetherAppend))
371  {
372  // The world is called x_nether, see if a world called x exists. If yes, choose it as the linked world,
373  // otherwise, choose the default world as the linked world.
374  // As before, any ini settings will completely override this if an ini is already present.
375 
376  LinkTo = WorldName.substr(0, WorldName.size() - NetherAppend.size());
377  if (GetWorld(LinkTo) == nullptr)
378  {
379  LinkTo = DefaultWorldName;
380  }
381  Dimension = dimNether;
382  }
383  // if the world is called x_the_end
384  else if ((LowercaseName.size() > EndAppend1.size()) && (LowercaseName.substr(LowercaseName.size() - EndAppend1.size()) == EndAppend1))
385  {
386  // The world is called x_the_end, see if a world called x exists. If yes, choose it as the linked world,
387  // otherwise, choose the default world as the linked world.
388  // As before, any ini settings will completely override this if an ini is already present.
389 
390  LinkTo = WorldName.substr(0, WorldName.size() - EndAppend1.size());
391  if (GetWorld(LinkTo) == nullptr)
392  {
393  LinkTo = DefaultWorldName;
394  }
395  Dimension = dimEnd;
396  }
397  // if the world is called x_end
398  else if ((LowercaseName.size() > EndAppend2.size()) && (LowercaseName.substr(LowercaseName.size() - EndAppend2.size()) == EndAppend2))
399  {
400  // The world is called x_end, see if a world called x exists. If yes, choose it as the linked world,
401  // otherwise, choose the default world as the linked world.
402  // As before, any ini settings will completely override this if an ini is already present.
403 
404  LinkTo = WorldName.substr(0, WorldName.size() - EndAppend2.size());
405  if (GetWorld(LinkTo) == nullptr)
406  {
407  LinkTo = DefaultWorldName;
408  }
409  Dimension = dimEnd;
410  }
411  m_WorldsByName.try_emplace(WorldName, WorldName, WorldPath, a_dd, WorldNames, Dimension, LinkTo);
412  } // for i - Worlds
413 
414  if (!FoundAdditionalWorlds)
415  {
416  if (a_Settings.GetKeyComment("Worlds", 0) != " World=secondworld")
417  {
418  a_Settings.DeleteKeyComment("Worlds", 0);
419  a_Settings.AddKeyComment("Worlds", " World=secondworld");
420  }
421  }
422 }
423 
424 
425 
426 
427 
428 void cRoot::StartWorlds(cDeadlockDetect & a_DeadlockDetect)
429 {
430  for (auto & Entry : m_WorldsByName)
431  {
432  auto & World = Entry.second;
433  World.Start();
434  World.InitializeSpawn();
436  }
437 }
438 
439 
440 
441 
442 
443 void cRoot::StopWorlds(cDeadlockDetect & a_DeadlockDetect)
444 {
445  for (auto & Entry : m_WorldsByName)
446  {
447  Entry.second.Stop(a_DeadlockDetect);
448  }
449 }
450 
451 
452 
453 
454 
456 {
457  ASSERT(m_pDefaultWorld != nullptr);
458  return m_pDefaultWorld;
459 }
460 
461 
462 
463 
464 
465 cWorld * cRoot::GetWorld(const AString & a_WorldName)
466 {
467  const auto FindResult = m_WorldsByName.find(a_WorldName);
468  if (FindResult != m_WorldsByName.cend())
469  {
470  return &FindResult->second;
471  }
472 
473  return nullptr;
474 }
475 
476 
477 
478 
479 
481 {
482  for (auto & World : m_WorldsByName)
483  {
484  if (a_Callback(World.second))
485  {
486  return false;
487  }
488  }
489  return true;
490 }
491 
492 
493 
494 
495 
497 {
498  const auto KickPlayers = [this]
499  {
500  // Kick all players from the server with custom disconnect message
501 
502  bool SentDisconnect = false;
504  [&](cPlayer & a_Player)
505  {
506  a_Player.GetClientHandle()->Kick(m_Server->GetShutdownMessage());
507  SentDisconnect = true;
508  return false;
509  }
510  );
511 
512  if (SentDisconnect)
513  {
514  std::this_thread::sleep_for(std::chrono::seconds(1));
515  }
516  };
517 
518  // Some commands are built-in:
519  if (a_Cmd == "stop")
520  {
521  KickPlayers();
522  cRoot::Stop();
523  return;
524  }
525  else if (a_Cmd == "restart")
526  {
527  KickPlayers();
528  cRoot::Restart();
529  return;
530  }
531 
532  LOG("Executing console command: \"%s\"", a_Cmd.c_str());
533  m_Server->ExecuteConsoleCommand(a_Cmd, a_Output);
534 }
535 
536 
537 
538 
539 
541 {
542  // Put the command into a queue (Alleviates FS #363):
544 }
545 
546 
547 
548 
549 
550 void cRoot::KickUser(int a_ClientID, const AString & a_Reason)
551 {
552  m_Server->KickUser(a_ClientID, a_Reason);
553 }
554 
555 
556 
557 
558 
560 {
561  size_t Count = 0;
562  for (const auto & Entry : m_WorldsByName)
563  {
564  Count += Entry.second.GetNumChunks();
565  }
566  return Count;
567 }
568 
569 
570 
571 
572 
574 {
575  for (auto & Entry : m_WorldsByName)
576  {
577  Entry.second.QueueSaveAllChunks();
578  }
579 }
580 
581 
582 
583 
584 
586 {
587  for (auto & Entry : m_WorldsByName)
588  {
589  Entry.second.SaveAllChunks();
590  }
591 }
592 
593 
594 
595 
596 
597 void cRoot::SetSavingEnabled(bool a_SavingEnabled)
598 {
599  for (auto & Entry : m_WorldsByName)
600  {
601  Entry.second.SetSavingEnabled(a_SavingEnabled);
602  }
603 }
604 
605 
606 
607 
608 
609 void cRoot::SendPlayerLists(cPlayer * a_DestPlayer)
610 {
611  for (auto & Entry : m_WorldsByName)
612  {
613  Entry.second.SendPlayerList(a_DestPlayer);
614  }
615 }
616 
617 
618 
619 
620 
621 void cRoot::BroadcastPlayerListsAddPlayer(const cPlayer & a_Player, const cClientHandle * a_Exclude)
622 {
623  for (auto & Entry : m_WorldsByName)
624  {
625  Entry.second.BroadcastPlayerListAddPlayer(a_Player);
626  }
627 }
628 
629 
630 
631 
632 
633 void cRoot::BroadcastPlayerListsRemovePlayer(const cPlayer & a_Player, const cClientHandle * a_Exclude)
634 {
635  for (auto & Entry : m_WorldsByName)
636  {
637  Entry.second.BroadcastPlayerListRemovePlayer(a_Player);
638  }
639 }
640 
641 
642 
643 
644 
646 {
647  for (auto & Entry : m_WorldsByName)
648  {
649  Entry.second.BroadcastPlayerListHeaderFooter(a_Header, a_Footer);
650  }
651 }
652 
653 
654 
655 
656 
657 void cRoot::BroadcastChat(const AString & a_Message, eMessageType a_ChatPrefix)
658 {
659  for (auto & Entry : m_WorldsByName)
660  {
661  Entry.second.BroadcastChat(a_Message, nullptr, a_ChatPrefix);
662  }
663 }
664 
665 
666 
667 
668 
669 void cRoot::BroadcastChat(const cCompositeChat & a_Message)
670 {
671  for (auto & Entry : m_WorldsByName)
672  {
673  Entry.second.BroadcastChat(a_Message);
674  }
675 }
676 
677 
678 
679 
680 
682 {
683  for (auto & Entry : m_WorldsByName)
684  {
685  if (!Entry.second.ForEachPlayer(a_Callback))
686  {
687  return false;
688  }
689  }
690  return true;
691 }
692 
693 
694 
695 
696 
697 bool cRoot::FindAndDoWithPlayer(const AString & a_PlayerName, cPlayerListCallback a_Callback)
698 {
699  class cCallback
700  {
701  size_t m_BestRating;
702  size_t m_NameLength;
703  const AString m_PlayerName;
704 
705  public:
706 
707  bool operator () (cPlayer & a_Player)
708  {
709  size_t Rating = RateCompareString (m_PlayerName, a_Player.GetName());
710  if ((Rating > 0) && (Rating >= m_BestRating))
711  {
712  m_BestMatch = a_Player.GetName();
713  if (Rating > m_BestRating)
714  {
715  m_NumMatches = 0;
716  }
717  m_BestRating = Rating;
718  ++m_NumMatches;
719  }
720  return (Rating == m_NameLength); // Perfect match
721  }
722 
723  cCallback (const AString & a_CBPlayerName) :
724  m_BestRating(0),
725  m_NameLength(a_CBPlayerName.length()),
726  m_PlayerName(a_CBPlayerName),
727  m_BestMatch(),
728  m_NumMatches(0)
729  {}
730 
731  AString m_BestMatch;
732  unsigned m_NumMatches;
733  } Callback (a_PlayerName);
734  ForEachPlayer(Callback);
735 
736  if (Callback.m_NumMatches == 1)
737  {
738  return DoWithPlayer(Callback.m_BestMatch, a_Callback);
739  }
740  return false;
741 }
742 
743 
744 
745 
746 
747 bool cRoot::DoWithPlayerByUUID(const cUUID & a_PlayerUUID, cPlayerListCallback a_Callback)
748 {
749  for (auto & Entry : m_WorldsByName)
750  {
751  if (Entry.second.DoWithPlayerByUUID(a_PlayerUUID, a_Callback))
752  {
753  return true;
754  }
755  }
756  return false;
757 }
758 
759 
760 
761 
762 
763 bool cRoot::DoWithPlayer(const AString & a_PlayerName, cPlayerListCallback a_Callback)
764 {
765  for (auto & Entry : m_WorldsByName)
766  {
767  if (Entry.second.DoWithPlayer(a_PlayerName, a_Callback))
768  {
769  return true;
770  }
771  }
772  return false;
773 }
774 
775 
776 
777 
778 
780 {
781  return cMultiVersionProtocol::GetVersionTextFromInt(static_cast<cProtocol::Version>(a_ProtocolVersion));
782 }
783 
784 
785 
786 
787 
789 {
790  #ifdef _WIN32
791  PROCESS_MEMORY_COUNTERS_EX pmc;
792  if (GetProcessMemoryInfo(GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS *)&pmc, sizeof(pmc)))
793  {
794  return (int)(pmc.PrivateUsage / 1024);
795  }
796  return -1;
797  #elif defined(__linux__)
798  // Code adapted from https://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-inside-a-process
799  std::ifstream StatFile("/proc/self/status");
800  if (!StatFile.good())
801  {
802  return -1;
803  }
804  while (StatFile.good())
805  {
806  AString Line;
807  std::getline(StatFile, Line);
808  if (strncmp(Line.c_str(), "VmSize:", 7) == 0)
809  {
810  int res = atoi(Line.c_str() + 8);
811  return (res == 0) ? -1 : res; // If parsing failed, return -1
812  }
813  }
814  return -1;
815  #elif defined (__APPLE__)
816  // Code adapted from https://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-inside-a-process
817  struct task_basic_info t_info;
818  mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
819 
820  if (KERN_SUCCESS == task_info(
821  mach_task_self(),
822  TASK_BASIC_INFO,
823  reinterpret_cast<task_info_t>(&t_info),
824  &t_info_count
825  ))
826  {
827  return static_cast<int>(t_info.virtual_size / 1024);
828  }
829  return -1;
830  #else
831  LOGINFO("%s: Unknown platform, cannot query memory usage", __FUNCTION__);
832  return -1;
833  #endif
834 }
835 
836 
837 
838 
839 
841 {
842  #ifdef _WIN32
843  PROCESS_MEMORY_COUNTERS pmc;
844  if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
845  {
846  return (int)(pmc.WorkingSetSize / 1024);
847  }
848  return -1;
849  #elif defined(__linux__)
850  // Code adapted from https://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-inside-a-process
851  std::ifstream StatFile("/proc/self/status");
852  if (!StatFile.good())
853  {
854  return -1;
855  }
856  while (StatFile.good())
857  {
858  AString Line;
859  std::getline(StatFile, Line);
860  if (strncmp(Line.c_str(), "VmRSS:", 6) == 0)
861  {
862  int res = atoi(Line.c_str() + 7);
863  return (res == 0) ? -1 : res; // If parsing failed, return -1
864  }
865  }
866  return -1;
867  #elif defined (__APPLE__)
868  // Code adapted from https://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-inside-a-process
869  struct task_basic_info t_info;
870  mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
871 
872  if (KERN_SUCCESS == task_info(
873  mach_task_self(),
874  TASK_BASIC_INFO,
875  reinterpret_cast<task_info_t>(&t_info),
876  &t_info_count
877  ))
878  {
879  return static_cast<int>(t_info.resident_size / 1024);
880  }
881  return -1;
882  #elif defined (__FreeBSD__)
883  /*
884  struct rusage self_usage;
885  int status = getrusage(RUSAGE_SELF, &self_usage);
886  if (!status)
887  {
888  return static_cast<int>(self_usage.ru_maxrss);
889  }
890  return -1;
891  */
892  // Good to watch: https://www.youtube.com/watch?v=Os5cK0H8EOA - getrusage.
893  // Unfortunately, it only gives peak memory usage a.k.a max resident set size
894  // So it is better to use FreeBSD kvm function to get the size of resident pages.
895 
896  static kvm_t* kd = NULL;
897 
898  if (kd == NULL)
899  {
900  kd = kvm_open(NULL, "/dev/null", NULL, O_RDONLY, "kvm_open"); // returns a descriptor used to access kernel virtual memory
901  }
902  if (kd != NULL)
903  {
904  int pc = 0; // number of processes found
905  struct kinfo_proc* kp;
906  kp = kvm_getprocs(kd, KERN_PROC_PID, getpid(), &pc);
907  if ((kp != NULL) && (pc >= 1))
908  {
909  return static_cast<int>(kp->ki_rssize * getpagesize() / 1024);
910  }
911  }
912  return -1;
913  #else
914  LOGINFO("%s: Unknown platform, cannot query memory usage", __FUNCTION__);
915  return -1;
916  #endif
917 }
918 
919 
920 
921 
922 
924 {
925  int SumNumValid = 0;
926  int SumNumDirty = 0;
927  int SumNumInLighting = 0;
928  size_t SumNumInGenerator = 0;
929  int SumMem = 0;
930  for (auto & Entry : m_WorldsByName)
931  {
932  auto & World = Entry.second;
933  const auto NumInGenerator = World.GetGeneratorQueueLength();
934  const auto NumInSaveQueue = World.GetStorageSaveQueueLength();
935  const auto NumInLoadQueue = World.GetStorageLoadQueueLength();
936  int NumValid = 0;
937  int NumDirty = 0;
938  int NumInLighting = 0;
939  World.GetChunkStats(NumValid, NumDirty, NumInLighting);
940  a_Output.OutLn(fmt::format(FMT_STRING("World {}:"), World.GetName()));
941  a_Output.OutLn(fmt::format(FMT_STRING(" Num loaded chunks: {}"), NumValid));
942  a_Output.OutLn(fmt::format(FMT_STRING(" Num dirty chunks: {}"), NumDirty));
943  a_Output.OutLn(fmt::format(FMT_STRING(" Num chunks in lighting queue: {}"), NumInLighting));
944  a_Output.OutLn(fmt::format(FMT_STRING(" Num chunks in generator queue: {}"), NumInGenerator));
945  a_Output.OutLn(fmt::format(FMT_STRING(" Num chunks in storage load queue: {}"), NumInLoadQueue));
946  a_Output.OutLn(fmt::format(FMT_STRING(" Num chunks in storage save queue: {}"), NumInSaveQueue));
947  int Mem = NumValid * static_cast<int>(sizeof(cChunk));
948  a_Output.OutLn(fmt::format(FMT_STRING(" Memory used by chunks: {} KiB ({} MiB)"), (Mem + 1023) / 1024, (Mem + 1024 * 1024 - 1) / (1024 * 1024)));
949  SumNumValid += NumValid;
950  SumNumDirty += NumDirty;
951  SumNumInLighting += NumInLighting;
952  SumNumInGenerator += NumInGenerator;
953  SumMem += Mem;
954  }
955  a_Output.OutLn("Totals:");
956  a_Output.OutLn(fmt::format(FMT_STRING(" Num loaded chunks: {}"), SumNumValid));
957  a_Output.OutLn(fmt::format(FMT_STRING(" Num dirty chunks: {}"), SumNumDirty));
958  a_Output.OutLn(fmt::format(FMT_STRING(" Num chunks in lighting queue: {}"), SumNumInLighting));
959  a_Output.OutLn(fmt::format(FMT_STRING(" Num chunks in generator queue: {}"), SumNumInGenerator));
960  a_Output.OutLn(fmt::format(FMT_STRING(" Memory used by chunks: {} KiB ({} MiB)"), (SumMem + 1023) / 1024, (SumMem + 1024 * 1024 - 1) / (1024 * 1024)));
961  a_Output.OutLn("Per-chunk memory size breakdown:");
962  a_Output.OutLn(fmt::format(FMT_STRING(" block types: {:06} bytes ({:3} KiB)"), sizeof(cChunkDef::BlockTypes), (sizeof(cChunkDef::BlockTypes) + 1023) / 1024));
963  a_Output.OutLn(fmt::format(FMT_STRING(" block metadata: {:06} bytes ({:3} KiB)"), sizeof(cChunkDef::BlockNibbles), (sizeof(cChunkDef::BlockNibbles) + 1023) / 1024));
964  a_Output.OutLn(fmt::format(FMT_STRING(" block lighting: {:06} bytes ({:3} KiB)"), 2 * sizeof(cChunkDef::BlockNibbles), (2 * sizeof(cChunkDef::BlockNibbles) + 1023) / 1024));
965  a_Output.OutLn(fmt::format(FMT_STRING(" heightmap: {:06} bytes ({:3} KiB)"), sizeof(cChunkDef::HeightMap), (sizeof(cChunkDef::HeightMap) + 1023) / 1024));
966  a_Output.OutLn(fmt::format(FMT_STRING(" biomemap: {:06} bytes ({:3} KiB)"), sizeof(cChunkDef::BiomeMap), (sizeof(cChunkDef::BiomeMap) + 1023) / 1024));
967 }
968 
969 
970 
971 
972 
974 {
975  cFurnaceRecipe * FR = Get()->GetFurnaceRecipe();
976  return FR->GetBurnTime(a_Fuel);
977 }
978 
979 
980 
981 
982 
984 {
985  AStringVector Results;
986  ForEachWorld([&](cWorld & a_World)
987  {
988  a_World.TabCompleteUserName(a_Text, Results);
989  return false;
990  }
991  );
992  return Results;
993 }
994 
995 
996 
997 
998 
1000 {
1001  if (g_RunAsService)
1002  {
1003  // Ignore input when running as a service, cin was never opened in that case:
1004  return;
1005  }
1006 
1008  AString Command;
1009 
1010  while (s_NextState == NextState::Run)
1011  {
1012 #ifndef _WIN32
1013  timeval Timeout{ 0, 0 };
1014  Timeout.tv_usec = 100 * 1000; // 100 msec
1015 
1016  fd_set ReadSet;
1017  FD_ZERO(&ReadSet);
1018  FD_SET(STDIN_FILENO, &ReadSet);
1019 
1020  if (select(STDIN_FILENO + 1, &ReadSet, nullptr, nullptr, &Timeout) <= 0)
1021  {
1022  // Don't call getline because there's nothing to read
1023  continue;
1024  }
1025 #endif
1026 
1027  if (!std::getline(std::cin, Command))
1028  {
1029  cRoot::Stop();
1030  return;
1031  }
1032 
1033  if (s_NextState != NextState::Run)
1034  {
1035  // Already shutting down, can't execute commands
1036  break;
1037  }
1038 
1039  if (!Command.empty())
1040  {
1041  // Execute and clear command string when submitted
1042  QueueExecuteConsoleCommand(TrimString(Command), Output);
1043  }
1044  }
1045 }
1046 
1047 
1048 
1049 
1050 
1052 {
1053  {
1054  auto Current = s_NextState.load();
1055  do
1056  {
1057  // Stopping is final, so stops override restarts:
1058  if (Current == NextState::Stop)
1059  {
1060  return;
1061  }
1062  }
1063  while (!s_NextState.compare_exchange_strong(Current, a_NextState));
1064  }
1065 
1066  if (s_NextState == NextState::Run)
1067  {
1068  return;
1069  }
1070 
1071  s_StopEvent.Set();
1072 
1073 #ifdef WIN32
1074  DWORD Length;
1075  INPUT_RECORD Record
1076  {
1077  KEY_EVENT,
1078  {
1079  {
1080  TRUE,
1081  1,
1082  VK_RETURN,
1083  static_cast<WORD>(MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC)),
1084  { { VK_RETURN } },
1085  0
1086  }
1087  }
1088  };
1089 
1090  // Can't kill the input thread since it breaks cin (getline doesn't block / receive input on restart)
1091  // Apparently no way to unblock getline apart from CancelIoEx, but xoft wants Windows XP support
1092  // Only thing I can think of for now. Also, ignore the retval since sometimes there's no cin.
1093  WriteConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &Record, 1, &Length);
1094 #endif
1095 }
eDimension
Dimension of a world.
Definition: Defines.h:231
@ dimEnd
Definition: Defines.h:234
@ dimNether
Definition: Defines.h:232
@ dimOverworld
Definition: Defines.h:233
eMessageType
Definition: Defines.h:352
#define ASSERT(x)
Definition: Globals.h:276
std::unique_ptr< cLogger::cListener > MakeConsoleListener(bool a_IsService)
std::pair< bool, std::unique_ptr< cLogger::cListener > > MakeFileListener()
void LOG(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:55
#define LOGWARN
Definition: LoggerSimple.h:88
#define LOGD
Definition: LoggerSimple.h:83
void LOGINFO(std::string_view a_Format, const Args &... args)
Definition: LoggerSimple.h:61
bool g_RunAsService
If set to true, binary will attempt to run as a service.
Definition: main.cpp:23
AString TrimString(const AString &str)
Trims whitespace at both ends of the string.
AString StrToLower(const AString &s)
Returns a lower-cased copy of the string.
size_t RateCompareString(const AString &s1, const AString &s2)
Case-insensitive string comparison that returns a rating of equal-ness between [0 - s1....
std::vector< AString > AStringVector
Definition: StringUtils.h:12
std::string AString
Definition: StringUtils.h:11
Utilities to allow casting a cWorld to one of its interfaces without including World....
Definition: OpaqueWorld.h:13
void ReloadPluginsNow(void)
Reloads all plugins, defaulting to settings.ini for settings location.
bool CallHookWorldStarted(cWorld &a_World)
Definition: Chunk.h:36
BLOCKTYPE BlockTypes[NumBlocks]
The type used for block type operations and storage, AXIS_ORDER ordering.
Definition: ChunkDef.h:140
HEIGHTTYPE HeightMap[Width *Width]
The type used for any heightmap operations and storage; idx = x + Width * z; Height points to the hig...
Definition: ChunkDef.h:132
NIBBLETYPE BlockNibbles[NumBlocks/2]
The type used for block data in nibble format, AXIS_ORDER ordering.
Definition: ChunkDef.h:143
EMCSBiome BiomeMap[Width *Width]
The type used for any biomemap operations and storage inside Cuberite, using Cuberite biomes (need no...
Definition: ChunkDef.h:137
static float FASTBREAK_PERCENTAGE
The percentage how much a block has to be broken.
Definition: ClientHandle.h:65
Interface for a callback that receives command output The Out() function is called for any output the...
Definition: CommandOutput.h:16
void OutLn(const AString &aText)
Outputs the specified text, plus a newline.
Definition: CommandOutput.h:24
Sends all command output to a log, line by line, when the command finishes processing.
Definition: CommandOutput.h:81
Sends all command output to a log, line by line; deletes self when command finishes processing.
Definition: CommandOutput.h:94
Container for a single chat message composed of multiple functional parts.
Definition: CompositeChat.h:34
The crafting recipes are the configurations to build a result item out of a set of ingredient items.
void Start(int a_IntervalSec)
Starts the detection.
Definition: Player.h:29
cClientHandle * GetClientHandle(void) const
Definition: Player.h:276
const AString & GetName(void) const
Definition: Player.cpp:1299
int GetBurnTime(const cItem &a_Fuel) const
Returns the amount of time that the specified fuel burns, in ticks.
Definition: Item.h:37
static cLogger & GetInstance(void)
Definition: Logger.cpp:43
cAttachment AttachListener(std::unique_ptr< cListener > a_Listener)
Definition: Logger.cpp:115
void Wait(void)
Waits until the event has been set.
Definition: Event.cpp:23
void Set(void)
Sets the event - releases one thread that has been waiting in Wait().
Definition: Event.cpp:52
void Stop(void)
Signals the thread to terminate and waits until it's finished.
Definition: IsThread.cpp:48
void Stop(void)
Stops the authenticator thread.
void Start(cSettingsRepositoryInterface &a_Settings)
Starts the authenticator thread.
void Start(cSettingsRepositoryInterface &a_Settings, bool a_ShouldAuth)
Initializes the API; reads the settings from the specified ini file.
Definition: MojangAPI.cpp:235
Version
The protocol version number, received from the client in the Handshake packet.
Definition: Protocol.h:335
static AString GetVersionTextFromInt(cProtocol::Version a_ProtocolVersion)
Translates protocol version number into protocol version text: 49 -> "1.4.4".
The RecipeMapper handles the translation of crafting recipes into protocol specific recipe Ids.
Definition: RecipeMapper.h:14
cMonsterConfig * m_MonsterConfig
Definition: Root.h:219
AString m_SettingsFilename
which ini file to load settings from, default is settings.ini
Definition: Root.h:56
std::unique_ptr< cRecipeMapper > m_RecipeMapper
Definition: Root.h:222
cServer * m_Server
Definition: Root.h:218
void SetSavingEnabled(bool a_SavingEnabled)
Sets whether saving chunks is enabled in all worlds (overrides however the worlds were already set)
Definition: Root.cpp:597
void BroadcastPlayerListsHeaderFooter(const cCompositeChat &a_Header, const cCompositeChat &a_Footer)
Broadcast playerlist header and footer through all worlds.
Definition: Root.cpp:645
static cRoot * Get()
Definition: Root.h:52
cWorld * m_pDefaultWorld
Definition: Root.h:213
static int GetPhysicalRAMUsage(void)
Returns the amount of virtual RAM used, in KiB.
Definition: Root.cpp:840
cRoot(void)
Definition: Root.cpp:75
void BroadcastChat(const AString &a_Message, eMessageType a_ChatPrefix=mtCustom)
Sends a chat message to all connected clients (in all worlds)
Definition: Root.cpp:657
bool ForEachWorld(cWorldListCallback a_Callback)
Calls the callback for each world; returns true if the callback didn't abort (return true)
Definition: Root.cpp:480
bool FindAndDoWithPlayer(const AString &a_PlayerName, cPlayerListCallback a_Callback)
Finds a player from a partial or complete player name and calls the callback - case-insensitive.
Definition: Root.cpp:697
cPluginManager * m_PluginManager
Definition: Root.h:226
void SaveAllChunksNow(void)
Saves all chunks in all worlds synchronously (waits until dirty chunks have been sent to the ChunkSto...
Definition: Root.cpp:585
cFurnaceRecipe * GetFurnaceRecipe(void)
Definition: Root.h:94
void KickUser(int a_ClientID, const AString &a_Reason)
Kicks the user, no matter in what world they are.
Definition: Root.cpp:550
AStringVector GetPlayerTabCompletionMultiWorld(const AString &a_Text)
Returns the completions for a player name across all worlds.
Definition: Root.cpp:983
void SendPlayerLists(cPlayer *a_DestPlayer)
Send playerlist of all worlds to player.
Definition: Root.cpp:609
void StartWorlds(cDeadlockDetect &a_DeadlockDetect)
Starts each world's life.
Definition: Root.cpp:428
cCraftingRecipes * m_CraftingRecipes
Definition: Root.h:221
bool DoWithPlayerByUUID(const cUUID &a_PlayerUUID, cPlayerListCallback a_Callback)
Finds the player over his uuid and calls the callback.
Definition: Root.cpp:747
std::chrono::steady_clock::time_point m_StartTime
The current time where the startup of the server has been completed.
Definition: Root.h:108
cMojangAPI * m_MojangAPI
Definition: Root.h:228
void HandleInput()
Blocking reads and processes console input.
Definition: Root.cpp:999
void SaveAllChunks(void)
Saves all chunks in all worlds.
Definition: Root.cpp:573
cWorld * GetDefaultWorld(void)
Definition: Root.cpp:455
static cRoot * s_Root
Definition: Root.h:249
NextState
States that the global cRoot can be in.
Definition: Root.h:199
cWebAdmin * m_WebAdmin
Definition: Root.h:225
static void Restart()
Interrupts the server and restarts it, as if "/restart" was typed in the console.
Definition: Root.cpp:268
static void Stop()
Interrupts the server and stops it, as if "/stop" typed in the console.
Definition: Root.cpp:259
static int GetVirtualRAMUsage(void)
Returns the amount of virtual RAM used, in KiB.
Definition: Root.cpp:788
static int GetFurnaceFuelBurnTime(const cItem &a_Fuel)
Returns the (read-write) storage for registered block types.
Definition: Root.cpp:973
std::unique_ptr< cBrewingRecipes > m_BrewingRecipes
Definition: Root.h:224
static cEvent s_StopEvent
Definition: Root.h:216
bool DoWithPlayer(const AString &a_PlayerName, cPlayerListCallback a_Callback)
Finds the player using it's complete username and calls the callback.
Definition: Root.cpp:763
void QueueExecuteConsoleCommand(const AString &a_Cmd, cCommandOutputCallback &a_Output)
Queues a console command for execution through the cServer class.
Definition: Root.cpp:496
size_t GetTotalChunkCount(void)
Returns the number of chunks loaded.
Definition: Root.cpp:559
static AString GetProtocolVersionTextFromInt(int a_ProtocolVersionNum)
Returns the textual description of the protocol version: 49 -> "1.4.4".
Definition: Root.cpp:779
~cRoot()
Definition: Root.cpp:94
void StopWorlds(cDeadlockDetect &a_DeadlockDetect)
Stops each world's threads, so that it's safe to unload them.
Definition: Root.cpp:443
cWorld * GetWorld(const AString &a_WorldName)
Returns a pointer to the world specified.
Definition: Root.cpp:465
WorldMap m_WorldsByName
Definition: Root.h:214
static void TransitionNextState(NextState a_NextState)
Performs run state transition, enforcing guarantees about state transitions.
Definition: Root.cpp:1051
void BroadcastPlayerListsAddPlayer(const cPlayer &a_Player, const cClientHandle *a_Exclude=nullptr)
Broadcast playerlist addition through all worlds.
Definition: Root.cpp:621
void LoadGlobalSettings()
The storage for all registered block types.
Definition: Root.cpp:277
cAuthenticator m_Authenticator
Definition: Root.h:227
void BroadcastPlayerListsRemovePlayer(const cPlayer &a_Player, const cClientHandle *a_Exclude=nullptr)
Broadcast playerlist removal through all worlds.
Definition: Root.cpp:633
void LogChunkStats(cCommandOutputCallback &a_Output)
Writes chunkstats, for each world and totals, to the output callback.
Definition: Root.cpp:923
cFurnaceRecipe * m_FurnaceRecipe
Definition: Root.h:223
bool ForEachPlayer(cPlayerListCallback a_Callback)
Calls the callback for each player in all worlds.
Definition: Root.cpp:681
static std::atomic< NextState > s_NextState
Indicates the next action of cRoot, whether to run, stop or restart.
Definition: Root.h:252
void LoadWorlds(cDeadlockDetect &a_dd, cSettingsRepositoryInterface &a_Settings, bool a_IsNewIniFile)
Loads the worlds from settings.ini, creates the worldmap.
Definition: Root.cpp:286
std::unique_ptr< cRankManager > m_RankManager
Definition: Root.h:230
bool Run(cSettingsRepositoryInterface &a_OverridesRepo)
Run the server.
Definition: Root.cpp:103
Definition: Server.h:56
const AString & GetShutdownMessage(void) const
Definition: Server.h:67
void ExecuteConsoleCommand(const AString &a_Cmd, cCommandOutputCallback &a_Output)
Executes the console command, sends output through the specified callback.
Definition: Server.cpp:470
bool Start(void)
Definition: Server.cpp:400
bool InitServer(cSettingsRepositoryInterface &a_Settings, bool a_ShouldAuth)
Definition: Server.cpp:153
void Shutdown(void)
Definition: Server.cpp:671
void KickUser(int a_ClientID, const AString &a_Reason)
Definition: Server.cpp:699
virtual AString GetValueSet(const AString &keyname, const AString &valuename, const AString &defValue="")=0
Gets the value; if not found, write the default to the repository.
virtual bool AddKeyComment(const AString &keyname, const AString &comment)=0
Add a key comment, will always fail if the repository does not support comments.
virtual bool DeleteKeyComment(const AString &keyname, const int commentID)=0
Delete a key comment, will always fail if the repository does not support comments.
virtual std::vector< std::pair< AString, AString > > GetValues(AString a_keyName)=0
returns a vector containing a name, value pair for each value under the key
virtual AString GetValue(const AString &keyname, const AString &valuename, const AString &defValue="") const =0
Get the value at the specified key and value, returns defValue on failure.
virtual void AddValue(const AString &a_KeyName, const AString &a_ValueName, const AString &a_Value)=0
Adds a new value to the specified key.
virtual AString GetKeyComment(const AString &keyname, const int commentID) const =0
Return a key comment, returns "" for repositories that do not return comments.
virtual bool HasValue(const AString &a_KeyName, const AString &a_ValueName) const =0
Returns true iff the specified value exists.
Definition: UUID.h:11
void Stop(void)
Stops the HTTP server, if it was started.
Definition: WebAdmin.cpp:128
bool Start(void)
Starts the HTTP server taking care of the webadmin.
Definition: WebAdmin.cpp:110
bool Init(void)
Initializes the object.
Definition: WebAdmin.cpp:81
Definition: World.h:53
void TabCompleteUserName(const AString &a_Text, AStringVector &a_Results)
Appends all usernames starting with a_Text (case-insensitive) into Results.
Definition: World.cpp:2987