aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp19
-rw-r--r--Minecraft.Client/Minecraft.Client.vcxproj5
-rw-r--r--Minecraft.Client/MinecraftServer.cpp470
-rw-r--r--Minecraft.Client/MinecraftServer.h1
-rw-r--r--Minecraft.Client/Settings.cpp80
-rw-r--r--Minecraft.Client/Settings.h6
-rw-r--r--Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp21
-rw-r--r--Minecraft.Client/Windows64/Network/WinsockNetLayer.h5
-rw-r--r--Minecraft.Client/Windows64/Windows64_Minecraft.cpp639
-rw-r--r--Minecraft.World/Minecraft.World.vcxproj5
-rw-r--r--README.md12
12 files changed, 973 insertions, 291 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 74851754..2d83c5c8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,6 +17,7 @@ function(configure_msvc_target target)
$<$<AND:$<NOT:$<CONFIG:Release>>,$<COMPILE_LANGUAGE:C,CXX>>:/W3>
$<$<AND:$<CONFIG:Release>,$<COMPILE_LANGUAGE:C,CXX>>:/W0>
$<$<COMPILE_LANGUAGE:C,CXX>:/MP>
+ $<$<COMPILE_LANGUAGE:C,CXX>:/FS>
$<$<COMPILE_LANGUAGE:CXX>:/EHsc>
$<$<AND:$<CONFIG:Release>,$<COMPILE_LANGUAGE:C,CXX>>:/GL /O2 /Oi /GT /GF>
)
diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
index 10d1a6a5..c2466fe3 100644
--- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
+++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp
@@ -362,12 +362,23 @@ void CPlatformNetworkManagerStub::HostGame(int localUsersMask, bool bOnlineGame,
#ifdef _WINDOWS64
int port = WIN64_NET_DEFAULT_PORT;
+ const char* bindIp = NULL;
+ if (g_Win64DedicatedServer)
+ {
+ if (g_Win64DedicatedServerPort > 0)
+ port = g_Win64DedicatedServerPort;
+ if (g_Win64DedicatedServerBindIP[0] != 0)
+ bindIp = g_Win64DedicatedServerBindIP;
+ }
if (!WinsockNetLayer::IsActive())
- WinsockNetLayer::HostGame(port);
+ WinsockNetLayer::HostGame(port, bindIp);
- const wchar_t* hostName = IQNet::m_player[0].m_gamertag;
- unsigned int settings = app.GetGameHostOption(eGameHostOption_All);
- WinsockNetLayer::StartAdvertising(port, hostName, settings, 0, 0, MINECRAFT_NET_VERSION);
+ if (WinsockNetLayer::IsActive())
+ {
+ const wchar_t* hostName = IQNet::m_player[0].m_gamertag;
+ unsigned int settings = app.GetGameHostOption(eGameHostOption_All);
+ WinsockNetLayer::StartAdvertising(port, hostName, settings, 0, 0, MINECRAFT_NET_VERSION);
+ }
#endif
//#endif
}
diff --git a/Minecraft.Client/Minecraft.Client.vcxproj b/Minecraft.Client/Minecraft.Client.vcxproj
index 54915636..d7c49acf 100644
--- a/Minecraft.Client/Minecraft.Client.vcxproj
+++ b/Minecraft.Client/Minecraft.Client.vcxproj
@@ -1545,6 +1545,7 @@ if not exist "$(TargetDir)\savedata" mkdir "$(TargetDir)\savedata"</Command>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<ShowIncludes>false</ShowIncludes>
+ <AdditionalOptions>/FS %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -1773,7 +1774,7 @@ xcopy /q /y /i /s /e $(ProjectDir)DurangoMedia\CU $(LayoutDir)Image\Loose\CU</C
<IntrinsicFunctions>true</IntrinsicFunctions>
<EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
<StringPooling>true</StringPooling>
- <AdditionalOptions>/Ob3</AdditionalOptions>
+ <AdditionalOptions>/FS /Ob3 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -48677,4 +48678,4 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CU</Comman
<ImportGroup Label="ExtensionTargets">
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
</ImportGroup>
-</Project> \ No newline at end of file
+</Project>
diff --git a/Minecraft.Client/MinecraftServer.cpp b/Minecraft.Client/MinecraftServer.cpp
index 4206a399..974c7405 100644
--- a/Minecraft.Client/MinecraftServer.cpp
+++ b/Minecraft.Client/MinecraftServer.cpp
@@ -27,6 +27,11 @@
#include "..\Minecraft.World\Pos.h"
#include "..\Minecraft.World\System.h"
#include "..\Minecraft.World\StringHelpers.h"
+#include "..\Minecraft.World\net.minecraft.world.entity.item.h"
+#include "..\Minecraft.World\net.minecraft.world.item.h"
+#include "..\Minecraft.World\net.minecraft.world.item.enchantment.h"
+#include "..\Minecraft.World\net.minecraft.world.damagesource.h"
+#include <sstream>
#ifdef SPLIT_SAVES
#include "..\Minecraft.World\ConsoleSaveFileSplit.h"
#endif
@@ -78,6 +83,438 @@ bool MinecraftServer::s_slowQueuePacketSent = false;
unordered_map<wstring, int> MinecraftServer::ironTimers;
+static void PrintConsoleLine(const wchar_t *prefix, const wstring &message)
+{
+ wprintf(L"%ls%ls\n", prefix, message.c_str());
+ fflush(stdout);
+}
+
+static bool TryParseIntValue(const wstring &text, int &value)
+{
+ std::wistringstream stream(text);
+ stream >> value;
+ return !stream.fail() && stream.eof();
+}
+
+static vector<wstring> SplitConsoleCommand(const wstring &command)
+{
+ vector<wstring> tokens;
+ std::wistringstream stream(command);
+ wstring token;
+ while (stream >> token)
+ {
+ tokens.push_back(token);
+ }
+ return tokens;
+}
+
+static wstring JoinConsoleCommandTokens(const vector<wstring> &tokens, size_t startIndex)
+{
+ wstring joined;
+ for (size_t i = startIndex; i < tokens.size(); ++i)
+ {
+ if (!joined.empty()) joined += L" ";
+ joined += tokens[i];
+ }
+ return joined;
+}
+
+static shared_ptr<ServerPlayer> FindPlayerByName(PlayerList *playerList, const wstring &name)
+{
+ if (playerList == NULL) return nullptr;
+
+ for (size_t i = 0; i < playerList->players.size(); ++i)
+ {
+ shared_ptr<ServerPlayer> player = playerList->players[i];
+ if (player != NULL && equalsIgnoreCase(player->getName(), name))
+ {
+ return player;
+ }
+ }
+
+ return nullptr;
+}
+
+static void SetAllLevelTimes(MinecraftServer *server, int value)
+{
+ for (unsigned int i = 0; i < server->levels.length; ++i)
+ {
+ if (server->levels[i] != NULL)
+ {
+ server->levels[i]->setDayTime(value);
+ }
+ }
+}
+
+static bool ExecuteConsoleCommand(MinecraftServer *server, const wstring &rawCommand)
+{
+ if (server == NULL)
+ return false;
+
+ wstring command = trimString(rawCommand);
+ if (command.empty())
+ return true;
+
+ if (command[0] == L'/')
+ {
+ command = trimString(command.substr(1));
+ }
+
+ vector<wstring> tokens = SplitConsoleCommand(command);
+ if (tokens.empty())
+ return true;
+
+ const wstring action = toLower(tokens[0]);
+ PlayerList *playerList = server->getPlayers();
+
+ if (action == L"help" || action == L"?")
+ {
+ server->info(L"Commands: help, stop, list, say <message>, save-all, time <set day|night|ticks|add ticks>, weather <clear|rain|thunder> [seconds], tp <player> <target>, give <player> <itemId> [amount] [aux], enchant <player> <enchantId> [level], kill <player>");
+ return true;
+ }
+
+ if (action == L"stop")
+ {
+ server->info(L"Stopping server...");
+ MinecraftServer::HaltServer();
+ return true;
+ }
+
+ if (action == L"list")
+ {
+ wstring playerNames = (playerList != NULL) ? playerList->getPlayerNames() : L"";
+ if (playerNames.empty()) playerNames = L"(none)";
+ server->info(L"Players (" + _toString((playerList != NULL) ? playerList->getPlayerCount() : 0) + L"): " + playerNames);
+ return true;
+ }
+
+ if (action == L"say")
+ {
+ if (tokens.size() < 2)
+ {
+ server->warn(L"Usage: say <message>");
+ return false;
+ }
+
+ wstring message = L"[Server] " + JoinConsoleCommandTokens(tokens, 1);
+ if (playerList != NULL)
+ {
+ playerList->broadcastAll(shared_ptr<ChatPacket>(new ChatPacket(message)));
+ }
+ server->info(message);
+ return true;
+ }
+
+ if (action == L"save-all")
+ {
+ if (playerList != NULL)
+ {
+ playerList->saveAll(NULL, false);
+ }
+ server->info(L"World saved.");
+ return true;
+ }
+
+ if (action == L"time")
+ {
+ if (tokens.size() < 2)
+ {
+ server->warn(L"Usage: time set <day|night|ticks> | time add <ticks>");
+ return false;
+ }
+
+ if (toLower(tokens[1]) == L"add")
+ {
+ if (tokens.size() < 3)
+ {
+ server->warn(L"Usage: time add <ticks>");
+ return false;
+ }
+
+ int delta = 0;
+ if (!TryParseIntValue(tokens[2], delta))
+ {
+ server->warn(L"Invalid tick value: " + tokens[2]);
+ return false;
+ }
+
+ for (unsigned int i = 0; i < server->levels.length; ++i)
+ {
+ if (server->levels[i] != NULL)
+ {
+ server->levels[i]->setDayTime(server->levels[i]->getDayTime() + delta);
+ }
+ }
+
+ server->info(L"Added " + _toString(delta) + L" ticks.");
+ return true;
+ }
+
+ wstring timeValue = toLower(tokens[1]);
+ if (timeValue == L"set")
+ {
+ if (tokens.size() < 3)
+ {
+ server->warn(L"Usage: time set <day|night|ticks>");
+ return false;
+ }
+ timeValue = toLower(tokens[2]);
+ }
+
+ int targetTime = 0;
+ if (timeValue == L"day")
+ {
+ targetTime = 0;
+ }
+ else if (timeValue == L"night")
+ {
+ targetTime = 12500;
+ }
+ else if (!TryParseIntValue(timeValue, targetTime))
+ {
+ server->warn(L"Invalid time value: " + timeValue);
+ return false;
+ }
+
+ SetAllLevelTimes(server, targetTime);
+ server->info(L"Time set to " + _toString(targetTime) + L".");
+ return true;
+ }
+
+ if (action == L"weather")
+ {
+ if (tokens.size() < 2)
+ {
+ server->warn(L"Usage: weather <clear|rain|thunder> [seconds]");
+ return false;
+ }
+
+ int durationSeconds = 600;
+ if (tokens.size() >= 3 && !TryParseIntValue(tokens[2], durationSeconds))
+ {
+ server->warn(L"Invalid duration: " + tokens[2]);
+ return false;
+ }
+
+ if (server->levels[0] == NULL)
+ {
+ server->warn(L"The overworld is not loaded.");
+ return false;
+ }
+
+ LevelData *levelData = server->levels[0]->getLevelData();
+ int duration = durationSeconds * SharedConstants::TICKS_PER_SECOND;
+ levelData->setRainTime(duration);
+ levelData->setThunderTime(duration);
+
+ wstring weather = toLower(tokens[1]);
+ if (weather == L"clear")
+ {
+ levelData->setRaining(false);
+ levelData->setThundering(false);
+ }
+ else if (weather == L"rain")
+ {
+ levelData->setRaining(true);
+ levelData->setThundering(false);
+ }
+ else if (weather == L"thunder")
+ {
+ levelData->setRaining(true);
+ levelData->setThundering(true);
+ }
+ else
+ {
+ server->warn(L"Usage: weather <clear|rain|thunder> [seconds]");
+ return false;
+ }
+
+ server->info(L"Weather set to " + weather + L".");
+ return true;
+ }
+
+ if (action == L"tp" || action == L"teleport")
+ {
+ if (tokens.size() < 3)
+ {
+ server->warn(L"Usage: tp <player> <target>");
+ return false;
+ }
+
+ shared_ptr<ServerPlayer> subject = FindPlayerByName(playerList, tokens[1]);
+ shared_ptr<ServerPlayer> destination = FindPlayerByName(playerList, tokens[2]);
+ if (subject == NULL)
+ {
+ server->warn(L"Unknown player: " + tokens[1]);
+ return false;
+ }
+ if (destination == NULL)
+ {
+ server->warn(L"Unknown player: " + tokens[2]);
+ return false;
+ }
+ if (subject->level->dimension->id != destination->level->dimension->id || !subject->isAlive())
+ {
+ server->warn(L"Teleport failed because the players are not in the same dimension or the source player is dead.");
+ return false;
+ }
+
+ subject->ride(nullptr);
+ subject->connection->teleport(destination->x, destination->y, destination->z, destination->yRot, destination->xRot);
+ server->info(L"Teleported " + subject->getName() + L" to " + destination->getName() + L".");
+ return true;
+ }
+
+ if (action == L"give")
+ {
+ if (tokens.size() < 3)
+ {
+ server->warn(L"Usage: give <player> <itemId> [amount] [aux]");
+ return false;
+ }
+
+ shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
+ if (player == NULL)
+ {
+ server->warn(L"Unknown player: " + tokens[1]);
+ return false;
+ }
+
+ int itemId = 0;
+ int amount = 1;
+ int aux = 0;
+ if (!TryParseIntValue(tokens[2], itemId))
+ {
+ server->warn(L"Invalid item id: " + tokens[2]);
+ return false;
+ }
+ if (tokens.size() >= 4 && !TryParseIntValue(tokens[3], amount))
+ {
+ server->warn(L"Invalid amount: " + tokens[3]);
+ return false;
+ }
+ if (tokens.size() >= 5 && !TryParseIntValue(tokens[4], aux))
+ {
+ server->warn(L"Invalid aux value: " + tokens[4]);
+ return false;
+ }
+ if (itemId <= 0 || Item::items[itemId] == NULL)
+ {
+ server->warn(L"Unknown item id: " + _toString(itemId));
+ return false;
+ }
+ if (amount <= 0)
+ {
+ server->warn(L"Amount must be positive.");
+ return false;
+ }
+
+ shared_ptr<ItemInstance> itemInstance(new ItemInstance(itemId, amount, aux));
+ shared_ptr<ItemEntity> drop = player->drop(itemInstance);
+ if (drop != NULL)
+ {
+ drop->throwTime = 0;
+ }
+ server->info(L"Gave item " + _toString(itemId) + L" x" + _toString(amount) + L" to " + player->getName() + L".");
+ return true;
+ }
+
+ if (action == L"enchant")
+ {
+ if (tokens.size() < 3)
+ {
+ server->warn(L"Usage: enchant <player> <enchantId> [level]");
+ return false;
+ }
+
+ shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
+ if (player == NULL)
+ {
+ server->warn(L"Unknown player: " + tokens[1]);
+ return false;
+ }
+
+ int enchantmentId = 0;
+ int enchantmentLevel = 1;
+ if (!TryParseIntValue(tokens[2], enchantmentId))
+ {
+ server->warn(L"Invalid enchantment id: " + tokens[2]);
+ return false;
+ }
+ if (tokens.size() >= 4 && !TryParseIntValue(tokens[3], enchantmentLevel))
+ {
+ server->warn(L"Invalid enchantment level: " + tokens[3]);
+ return false;
+ }
+
+ shared_ptr<ItemInstance> selectedItem = player->getSelectedItem();
+ if (selectedItem == NULL)
+ {
+ server->warn(L"The player is not holding an item.");
+ return false;
+ }
+
+ Enchantment *enchantment = Enchantment::enchantments[enchantmentId];
+ if (enchantment == NULL)
+ {
+ server->warn(L"Unknown enchantment id: " + _toString(enchantmentId));
+ return false;
+ }
+ if (!enchantment->canEnchant(selectedItem))
+ {
+ server->warn(L"That enchantment cannot be applied to the selected item.");
+ return false;
+ }
+
+ if (enchantmentLevel < enchantment->getMinLevel()) enchantmentLevel = enchantment->getMinLevel();
+ if (enchantmentLevel > enchantment->getMaxLevel()) enchantmentLevel = enchantment->getMaxLevel();
+
+ if (selectedItem->hasTag())
+ {
+ ListTag<CompoundTag> *enchantmentTags = selectedItem->getEnchantmentTags();
+ if (enchantmentTags != NULL)
+ {
+ for (int i = 0; i < enchantmentTags->size(); i++)
+ {
+ int type = enchantmentTags->get(i)->getShort((wchar_t *)ItemInstance::TAG_ENCH_ID);
+ if (Enchantment::enchantments[type] != NULL && !Enchantment::enchantments[type]->isCompatibleWith(enchantment))
+ {
+ server->warn(L"That enchantment conflicts with an existing enchantment on the selected item.");
+ return false;
+ }
+ }
+ }
+ }
+
+ selectedItem->enchant(enchantment, enchantmentLevel);
+ server->info(L"Enchanted " + player->getName() + L"'s held item with " + _toString(enchantmentId) + L" " + _toString(enchantmentLevel) + L".");
+ return true;
+ }
+
+ if (action == L"kill")
+ {
+ if (tokens.size() < 2)
+ {
+ server->warn(L"Usage: kill <player>");
+ return false;
+ }
+
+ shared_ptr<ServerPlayer> player = FindPlayerByName(playerList, tokens[1]);
+ if (player == NULL)
+ {
+ server->warn(L"Unknown player: " + tokens[1]);
+ return false;
+ }
+
+ player->hurt(DamageSource::outOfWorld, 3.4e38f);
+ server->info(L"Killed " + player->getName() + L".");
+ return true;
+ }
+
+ server->warn(L"Unknown command: " + command);
+ return false;
+}
+
MinecraftServer::MinecraftServer()
{
// 4J - added initialisers
@@ -107,12 +544,14 @@ MinecraftServer::MinecraftServer()
forceGameType = false;
commandDispatcher = new ServerCommandDispatcher();
+ InitializeCriticalSection(&m_consoleInputCS);
DispenserBootstrap::bootStrap();
}
MinecraftServer::~MinecraftServer()
{
+ DeleteCriticalSection(&m_consoleInputCS);
}
bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DWORD initSettings, bool findSeed)
@@ -150,6 +589,15 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW
#endif
settings = new Settings(new File(L"server.properties"));
+ app.SetGameHostOption(eGameHostOption_Difficulty, settings->getInt(L"difficulty", app.GetGameHostOption(eGameHostOption_Difficulty)));
+ app.SetGameHostOption(eGameHostOption_GameType, settings->getInt(L"gamemode", app.GetGameHostOption(eGameHostOption_GameType)));
+ app.SetGameHostOption(eGameHostOption_Structures, settings->getBoolean(L"generate-structures", app.GetGameHostOption(eGameHostOption_Structures) > 0) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_BonusChest, settings->getBoolean(L"bonus-chest", app.GetGameHostOption(eGameHostOption_BonusChest) > 0) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_PvP, settings->getBoolean(L"pvp", app.GetGameHostOption(eGameHostOption_PvP) > 0) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_TrustPlayers, settings->getBoolean(L"trust-players", app.GetGameHostOption(eGameHostOption_TrustPlayers) > 0) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_FireSpreads, settings->getBoolean(L"fire-spreads", app.GetGameHostOption(eGameHostOption_FireSpreads) > 0) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_TNT, settings->getBoolean(L"tnt", app.GetGameHostOption(eGameHostOption_TNT) > 0) ? 1 : 0);
+
app.DebugPrintf("\n*** SERVER SETTINGS ***\n");
app.DebugPrintf("ServerSettings: host-friends-only is %s\n",(app.GetGameHostOption(eGameHostOption_FriendsOfFriends)>0)?"on":"off");
app.DebugPrintf("ServerSettings: game-type is %s\n",(app.GetGameHostOption(eGameHostOption_GameType)==0)?"Survival Mode":"Creative Mode");
@@ -169,11 +617,11 @@ bool MinecraftServer::initServer(__int64 seed, NetworkGameInitData *initData, DW
setAnimals(settings->getBoolean(L"spawn-animals", true));
setNpcsEnabled(settings->getBoolean(L"spawn-npcs", true));
- setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false); // settings->getBoolean(L"pvp", true);
+ setPvpAllowed(app.GetGameHostOption( eGameHostOption_PvP )>0?true:false);
// 4J Stu - We should never have hacked clients flying when they shouldn't be like the PC version, so enable flying always
// Fix for #46612 - TU5: Code: Multiplayer: A client can be banned for flying when accidentaly being blown by dynamite
- setFlightAllowed(true); //settings->getBoolean(L"allow-flight", false);
+ setFlightAllowed(settings->getBoolean(L"allow-flight", true));
// 4J Stu - Enabling flight to stop it kicking us when we use it
#ifdef _DEBUG_MENUS_ENABLED
@@ -1707,17 +2155,23 @@ void MinecraftServer::tick()
void MinecraftServer::handleConsoleInput(const wstring& msg, ConsoleInputSource *source)
{
+ EnterCriticalSection(&m_consoleInputCS);
consoleInput.push_back(new ConsoleInput(msg, source));
+ LeaveCriticalSection(&m_consoleInputCS);
}
void MinecraftServer::handleConsoleInputs()
{
- while (consoleInput.size() > 0)
+ vector<ConsoleInput *> pendingInputs;
+ EnterCriticalSection(&m_consoleInputCS);
+ pendingInputs.swap(consoleInput);
+ LeaveCriticalSection(&m_consoleInputCS);
+
+ for (size_t i = 0; i < pendingInputs.size(); ++i)
{
- AUTO_VAR(it, consoleInput.begin());
- ConsoleInput *input = *it;
- consoleInput.erase(it);
- // commands->handleCommand(input); // 4J - removed - TODO - do we want equivalent of console commands?
+ ConsoleInput *input = pendingInputs[i];
+ ExecuteConsoleCommand(this, input->msg);
+ delete input;
}
}
@@ -1750,10 +2204,12 @@ File *MinecraftServer::getFile(const wstring& name)
void MinecraftServer::info(const wstring& string)
{
+ PrintConsoleLine(L"[INFO] ", string);
}
void MinecraftServer::warn(const wstring& string)
{
+ PrintConsoleLine(L"[WARN] ", string);
}
wstring MinecraftServer::getConsoleName()
diff --git a/Minecraft.Client/MinecraftServer.h b/Minecraft.Client/MinecraftServer.h
index 5f33fa85..ec0218e0 100644
--- a/Minecraft.Client/MinecraftServer.h
+++ b/Minecraft.Client/MinecraftServer.h
@@ -103,6 +103,7 @@ private:
// vector<Tickable *> tickables = new ArrayList<Tickable>(); // 4J - removed
CommandDispatcher *commandDispatcher;
vector<ConsoleInput *> consoleInput; // 4J - was synchronizedList - TODO - investigate
+ CRITICAL_SECTION m_consoleInputCS;
public:
bool onlineMode;
bool animals;
diff --git a/Minecraft.Client/Settings.cpp b/Minecraft.Client/Settings.cpp
index 89946773..4223b001 100644
--- a/Minecraft.Client/Settings.cpp
+++ b/Minecraft.Client/Settings.cpp
@@ -1,18 +1,90 @@
#include "stdafx.h"
#include "Settings.h"
+#include "..\Minecraft.World\File.h"
#include "..\Minecraft.World\StringHelpers.h"
+#include <fstream>
+
+static wstring ParsePropertyText(const string &text)
+{
+ return trimString(convStringToWstring(text));
+}
+
+static bool TryParseBoolean(const wstring &text, bool defaultValue)
+{
+ wstring lowered = toLower(trimString(text));
+ if (lowered == L"true" || lowered == L"1" || lowered == L"yes" || lowered == L"on")
+ return true;
+ if (lowered == L"false" || lowered == L"0" || lowered == L"no" || lowered == L"off")
+ return false;
+ return defaultValue;
+}
-// 4J - TODO - serialise/deserialise from file
Settings::Settings(File *file)
{
+ if (file != NULL)
+ {
+ filePath = file->getPath();
+ }
+
+ if (filePath.empty())
+ return;
+
+ std::ifstream stream(wstringtofilename(filePath), std::ios::in | std::ios::binary);
+ if (!stream.is_open())
+ return;
+
+ string line;
+ while (std::getline(stream, line))
+ {
+ if (!line.empty() && line[line.size() - 1] == '\r')
+ line.erase(line.size() - 1);
+
+ if (line.size() >= 3 &&
+ (unsigned char)line[0] == 0xEF &&
+ (unsigned char)line[1] == 0xBB &&
+ (unsigned char)line[2] == 0xBF)
+ {
+ line.erase(0, 3);
+ }
+
+ size_t commentPos = line.find_first_of("#;");
+ if (commentPos != string::npos && line.find_first_not_of(" \t") == commentPos)
+ continue;
+
+ size_t separatorPos = line.find('=');
+ if (separatorPos == string::npos)
+ continue;
+
+ wstring key = ParsePropertyText(line.substr(0, separatorPos));
+ if (key.empty())
+ continue;
+
+ wstring value = ParsePropertyText(line.substr(separatorPos + 1));
+ properties[key] = value;
+ }
}
void Settings::generateNewProperties()
{
+ saveProperties();
}
void Settings::saveProperties()
{
+ if (filePath.empty())
+ return;
+
+ std::ofstream stream(wstringtofilename(filePath), std::ios::out | std::ios::binary | std::ios::trunc);
+ if (!stream.is_open())
+ return;
+
+ stream << "# MinecraftConsoles dedicated server properties\r\n";
+ for (unordered_map<wstring, wstring>::const_iterator it = properties.begin(); it != properties.end(); ++it)
+ {
+ string key = string(wstringtochararray(it->first));
+ string value = string(wstringtochararray(it->second));
+ stream << key << "=" << value << "\r\n";
+ }
}
wstring Settings::getString(const wstring& key, const wstring& defaultValue)
@@ -39,17 +111,17 @@ bool Settings::getBoolean(const wstring& key, bool defaultValue)
{
if(properties.find(key) == properties.end())
{
- properties[key] = _toString<bool>(defaultValue);
+ properties[key] = defaultValue ? L"true" : L"false";
saveProperties();
}
MemSect(35);
- bool retval = _fromString<bool>(properties[key]);
+ bool retval = TryParseBoolean(properties[key], defaultValue);
MemSect(0);
return retval;
}
void Settings::setBooleanAndSave(const wstring& key, bool value)
{
- properties[key] = _toString<bool>(value);
+ properties[key] = value ? L"true" : L"false";
saveProperties();
} \ No newline at end of file
diff --git a/Minecraft.Client/Settings.h b/Minecraft.Client/Settings.h
index b6a2c018..4a3c130b 100644
--- a/Minecraft.Client/Settings.h
+++ b/Minecraft.Client/Settings.h
@@ -7,8 +7,8 @@ class Settings
// public static Logger logger = Logger.getLogger("Minecraft");
// private Properties properties = new Properties();
private:
- unordered_map<wstring,wstring> properties; // 4J - TODO was Properties type, will need to implement something we can serialise/deserialise too
- //File *file;
+ unordered_map<wstring,wstring> properties;
+ wstring filePath;
public:
Settings(File *file);
@@ -18,4 +18,4 @@ public:
int getInt(const wstring& key, int defaultValue);
bool getBoolean(const wstring& key, bool defaultValue);
void setBooleanAndSave(const wstring& key, bool value);
-};
+}; \ No newline at end of file
diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
index d3ea1c3a..ca1d62af 100644
--- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
+++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp
@@ -51,6 +51,9 @@ bool g_Win64MultiplayerHost = false;
bool g_Win64MultiplayerJoin = false;
int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
char g_Win64MultiplayerIP[256] = "127.0.0.1";
+bool g_Win64DedicatedServer = false;
+int g_Win64DedicatedServerPort = WIN64_NET_DEFAULT_PORT;
+char g_Win64DedicatedServerBindIP[256] = "";
bool WinsockNetLayer::Initialize()
{
@@ -139,7 +142,7 @@ void WinsockNetLayer::Shutdown()
}
}
-bool WinsockNetLayer::HostGame(int port)
+bool WinsockNetLayer::HostGame(int port, const char* bindIp)
{
if (!s_initialized && !Initialize()) return false;
@@ -159,15 +162,19 @@ bool WinsockNetLayer::HostGame(int port)
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
- hints.ai_flags = AI_PASSIVE;
+ hints.ai_flags = (bindIp == NULL || bindIp[0] == 0) ? AI_PASSIVE : 0;
char portStr[16];
sprintf_s(portStr, "%d", port);
- int iResult = getaddrinfo(NULL, portStr, &hints, &result);
+ const char* resolvedBindIp = (bindIp != NULL && bindIp[0] != 0) ? bindIp : NULL;
+ int iResult = getaddrinfo(resolvedBindIp, portStr, &hints, &result);
if (iResult != 0)
{
- app.DebugPrintf("getaddrinfo failed: %d\n", iResult);
+ app.DebugPrintf("getaddrinfo failed for %s:%d - %d\n",
+ resolvedBindIp != NULL ? resolvedBindIp : "*",
+ port,
+ iResult);
return false;
}
@@ -206,7 +213,9 @@ bool WinsockNetLayer::HostGame(int port)
s_acceptThread = CreateThread(NULL, 0, AcceptThreadProc, NULL, 0, NULL);
- app.DebugPrintf("Win64 LAN: Hosting on port %d\n", port);
+ app.DebugPrintf("Win64 LAN: Hosting on %s:%d\n",
+ resolvedBindIp != NULL ? resolvedBindIp : "*",
+ port);
return true;
}
@@ -908,4 +917,4 @@ DWORD WINAPI WinsockNetLayer::DiscoveryThreadProc(LPVOID param)
return 0;
}
-#endif \ No newline at end of file
+#endif
diff --git a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h
index 029dd0a7..fd1280f7 100644
--- a/Minecraft.Client/Windows64/Network/WinsockNetLayer.h
+++ b/Minecraft.Client/Windows64/Network/WinsockNetLayer.h
@@ -65,7 +65,7 @@ public:
static bool Initialize();
static void Shutdown();
- static bool HostGame(int port);
+ static bool HostGame(int port, const char* bindIp = NULL);
static bool JoinGame(const char* ip, int port);
static bool SendToSmallId(BYTE targetSmallId, const void* data, int dataSize);
@@ -147,5 +147,8 @@ extern bool g_Win64MultiplayerHost;
extern bool g_Win64MultiplayerJoin;
extern int g_Win64MultiplayerPort;
extern char g_Win64MultiplayerIP[256];
+extern bool g_Win64DedicatedServer;
+extern int g_Win64DedicatedServerPort;
+extern char g_Win64DedicatedServerBindIP[256];
#endif
diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp
index 3d5eec00..ed6781a3 100644
--- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp
+++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp
@@ -4,7 +4,9 @@
#include "stdafx.h"
#include <assert.h>
+#include <iostream>
#include <ShellScalingApi.h>
+#include <shellapi.h>
#include "GameConfig\Minecraft.spa.h"
#include "..\MinecraftServer.h"
#include "..\LocalPlayer.h"
@@ -34,6 +36,7 @@
#include "Sentient\SentientManager.h"
#include "..\..\Minecraft.World\IntCache.h"
#include "..\Textures.h"
+#include "..\Settings.h"
#include "Resource.h"
#include "..\..\Minecraft.World\compression.h"
#include "..\..\Minecraft.World\OldChunkStorage.h"
@@ -95,6 +98,148 @@ wchar_t g_Win64UsernameW[17] = { 0 };
static bool g_isFullscreen = false;
static WINDOWPLACEMENT g_wpPrev = { sizeof(g_wpPrev) };
+struct Win64LaunchOptions
+{
+ int screenMode;
+ bool serverMode;
+};
+
+static void CopyWideArgToAnsi(LPCWSTR source, char* dest, size_t destSize)
+{
+ if (destSize == 0)
+ return;
+
+ dest[0] = 0;
+ if (source == NULL)
+ return;
+
+ WideCharToMultiByte(CP_ACP, 0, source, -1, dest, (int)destSize, NULL, NULL);
+ dest[destSize - 1] = 0;
+}
+
+static void ApplyScreenMode(int screenMode)
+{
+ switch (screenMode)
+ {
+ case 1:
+ g_iScreenWidth = 1280;
+ g_iScreenHeight = 720;
+ break;
+ case 2:
+ g_iScreenWidth = 640;
+ g_iScreenHeight = 480;
+ break;
+ case 3:
+ g_iScreenWidth = 720;
+ g_iScreenHeight = 408;
+ break;
+ default:
+ break;
+ }
+}
+
+static Win64LaunchOptions ParseLaunchOptions()
+{
+ Win64LaunchOptions options = {};
+ options.screenMode = 0;
+ options.serverMode = false;
+
+ g_Win64MultiplayerJoin = false;
+ g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
+ g_Win64DedicatedServer = false;
+ g_Win64DedicatedServerPort = WIN64_NET_DEFAULT_PORT;
+ g_Win64DedicatedServerBindIP[0] = 0;
+
+ int argc = 0;
+ LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
+ if (argv == NULL)
+ return options;
+
+ if (argc > 1 && lstrlenW(argv[1]) == 1)
+ {
+ if (argv[1][0] >= L'1' && argv[1][0] <= L'3')
+ options.screenMode = argv[1][0] - L'0';
+ }
+
+ for (int i = 1; i < argc; ++i)
+ {
+ if (_wcsicmp(argv[i], L"-server") == 0)
+ {
+ options.serverMode = true;
+ break;
+ }
+ }
+
+ g_Win64DedicatedServer = options.serverMode;
+
+ for (int i = 1; i < argc; ++i)
+ {
+ if (_wcsicmp(argv[i], L"-name") == 0 && (i + 1) < argc)
+ {
+ CopyWideArgToAnsi(argv[++i], g_Win64Username, sizeof(g_Win64Username));
+ }
+ else if (_wcsicmp(argv[i], L"-ip") == 0 && (i + 1) < argc)
+ {
+ char ipBuf[256];
+ CopyWideArgToAnsi(argv[++i], ipBuf, sizeof(ipBuf));
+ if (options.serverMode)
+ {
+ strncpy_s(g_Win64DedicatedServerBindIP, sizeof(g_Win64DedicatedServerBindIP), ipBuf, _TRUNCATE);
+ }
+ else
+ {
+ strncpy_s(g_Win64MultiplayerIP, sizeof(g_Win64MultiplayerIP), ipBuf, _TRUNCATE);
+ g_Win64MultiplayerJoin = true;
+ }
+ }
+ else if (_wcsicmp(argv[i], L"-port") == 0 && (i + 1) < argc)
+ {
+ wchar_t* endPtr = NULL;
+ long port = wcstol(argv[++i], &endPtr, 10);
+ if (endPtr != argv[i] && *endPtr == 0 && port > 0 && port <= 65535)
+ {
+ if (options.serverMode)
+ g_Win64DedicatedServerPort = (int)port;
+ else
+ g_Win64MultiplayerPort = (int)port;
+ }
+ }
+ }
+
+ LocalFree(argv);
+ return options;
+}
+
+static BOOL WINAPI HeadlessServerCtrlHandler(DWORD ctrlType)
+{
+ switch (ctrlType)
+ {
+ case CTRL_C_EVENT:
+ case CTRL_BREAK_EVENT:
+ case CTRL_CLOSE_EVENT:
+ case CTRL_SHUTDOWN_EVENT:
+ app.m_bShutdown = true;
+ MinecraftServer::HaltServer();
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+static void SetupHeadlessServerConsole()
+{
+ if (AllocConsole())
+ {
+ FILE* stream = NULL;
+ freopen_s(&stream, "CONIN$", "r", stdin);
+ freopen_s(&stream, "CONOUT$", "w", stdout);
+ freopen_s(&stream, "CONOUT$", "w", stderr);
+ SetConsoleTitleA("Minecraft Server");
+ }
+
+ SetConsoleCtrlHandler(HeadlessServerCtrlHandler, TRUE);
+}
+
void DefineActions(void)
{
// The app needs to define the actions required, and the possible mappings for these
@@ -722,6 +867,225 @@ void CleanupDevice()
if( g_pd3dDevice ) g_pd3dDevice->Release();
}
+static Minecraft* InitialiseMinecraftRuntime()
+{
+ app.loadMediaArchive();
+
+ RenderManager.Initialise(g_pd3dDevice, g_pSwapChain);
+
+ app.loadStringTable();
+ ui.init(g_pd3dDevice, g_pImmediateContext, g_pRenderTargetView, g_pDepthStencilView, g_iScreenWidth, g_iScreenHeight);
+
+ InputManager.Initialise(1, 3, MINECRAFT_ACTION_MAX, ACTION_MAX_MENU);
+ KMInput.Init(g_hWnd);
+ DefineActions();
+ InputManager.SetJoypadMapVal(0, 0);
+ InputManager.SetKeyRepeatRate(0.3f, 0.2f);
+
+ ProfileManager.Initialise(TITLEID_MINECRAFT,
+ app.m_dwOfferID,
+ PROFILE_VERSION_10,
+ NUM_PROFILE_VALUES,
+ NUM_PROFILE_SETTINGS,
+ dwProfileSettingsA,
+ app.GAME_DEFINED_PROFILE_DATA_BYTES * XUSER_MAX_COUNT,
+ &app.uiGameDefinedDataChangedBitmask
+ );
+ ProfileManager.SetDefaultOptionsCallback(&CConsoleMinecraftApp::DefaultOptionsCallback, (LPVOID)&app);
+
+ g_NetworkManager.Initialise();
+
+ for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++)
+ {
+ IQNet::m_player[i].m_smallId = (BYTE)i;
+ IQNet::m_player[i].m_isRemote = false;
+ IQNet::m_player[i].m_isHostPlayer = (i == 0);
+ swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i);
+ }
+ wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW);
+
+ WinsockNetLayer::Initialize();
+
+ ProfileManager.SetDebugFullOverride(true);
+
+ Tesselator::CreateNewThreadStorage(1024 * 1024);
+ AABB::CreateNewThreadStorage();
+ Vec3::CreateNewThreadStorage();
+ IntCache::CreateNewThreadStorage();
+ Compression::CreateNewThreadStorage();
+ OldChunkStorage::CreateNewThreadStorage();
+ Level::enableLightingCache();
+ Tile::CreateNewThreadStorage();
+
+ Minecraft::main();
+ Minecraft* pMinecraft = Minecraft::GetInstance();
+ if (pMinecraft == NULL)
+ return NULL;
+
+ app.InitGameSettings();
+ app.InitialiseTips();
+
+ pMinecraft->options->set(Options::Option::MUSIC, 1.0f);
+ pMinecraft->options->set(Options::Option::SOUND, 1.0f);
+
+ return pMinecraft;
+}
+
+static int HeadlessServerConsoleThreadProc(void* lpParameter)
+{
+ UNREFERENCED_PARAMETER(lpParameter);
+
+ std::string line;
+ while (!app.m_bShutdown)
+ {
+ if (!std::getline(std::cin, line))
+ {
+ if (std::cin.eof())
+ {
+ break;
+ }
+
+ std::cin.clear();
+ Sleep(50);
+ continue;
+ }
+
+ wstring command = trimString(convStringToWstring(line));
+ if (command.empty())
+ continue;
+
+ MinecraftServer* server = MinecraftServer::getInstance();
+ if (server != NULL)
+ {
+ server->handleConsoleInput(command, server);
+ }
+ }
+
+ return 0;
+}
+
+static int RunHeadlessServer()
+{
+ SetupHeadlessServerConsole();
+
+ Settings serverSettings(new File(L"server.properties"));
+ wstring configuredBindIp = serverSettings.getString(L"server-ip", L"");
+
+ const char* bindIp = "*";
+ if (g_Win64DedicatedServerBindIP[0] != 0)
+ {
+ bindIp = g_Win64DedicatedServerBindIP;
+ }
+ else if (!configuredBindIp.empty())
+ {
+ bindIp = wstringtochararray(configuredBindIp);
+ }
+
+ const int port = g_Win64DedicatedServerPort > 0 ? g_Win64DedicatedServerPort : serverSettings.getInt(L"server-port", WIN64_NET_DEFAULT_PORT);
+
+ printf("Starting headless server on %s:%d\n", bindIp, port);
+ fflush(stdout);
+
+ Minecraft* pMinecraft = InitialiseMinecraftRuntime();
+ if (pMinecraft == NULL)
+ {
+ fprintf(stderr, "Failed to initialise the Minecraft runtime.\n");
+ return 1;
+ }
+
+ app.SetGameHostOption(eGameHostOption_Difficulty, serverSettings.getInt(L"difficulty", 1));
+ app.SetGameHostOption(eGameHostOption_Gamertags, 1);
+ app.SetGameHostOption(eGameHostOption_GameType, serverSettings.getInt(L"gamemode", 0));
+ app.SetGameHostOption(eGameHostOption_LevelType, 0);
+ app.SetGameHostOption(eGameHostOption_Structures, serverSettings.getBoolean(L"generate-structures", true) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_BonusChest, serverSettings.getBoolean(L"bonus-chest", false) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_PvP, serverSettings.getBoolean(L"pvp", true) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_TrustPlayers, serverSettings.getBoolean(L"trust-players", true) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_FireSpreads, serverSettings.getBoolean(L"fire-spreads", true) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_TNT, serverSettings.getBoolean(L"tnt", true) ? 1 : 0);
+ app.SetGameHostOption(eGameHostOption_HostCanFly, 1);
+ app.SetGameHostOption(eGameHostOption_HostCanChangeHunger, 1);
+ app.SetGameHostOption(eGameHostOption_HostCanBeInvisible, 1);
+ app.SetGameHostOption(eGameHostOption_MobGriefing, 1);
+ app.SetGameHostOption(eGameHostOption_KeepInventory, 0);
+ app.SetGameHostOption(eGameHostOption_DoMobSpawning, 1);
+ app.SetGameHostOption(eGameHostOption_DoMobLoot, 1);
+ app.SetGameHostOption(eGameHostOption_DoTileDrops, 1);
+ app.SetGameHostOption(eGameHostOption_NaturalRegeneration, 1);
+ app.SetGameHostOption(eGameHostOption_DoDaylightCycle, 1);
+
+ MinecraftServer::resetFlags();
+ g_NetworkManager.HostGame(0, false, true, MINECRAFT_NET_MAX_PLAYERS, 0);
+
+ if (!WinsockNetLayer::IsActive())
+ {
+ fprintf(stderr, "Failed to bind the server socket on %s:%d.\n", bindIp, port);
+ return 1;
+ }
+
+ g_NetworkManager.FakeLocalPlayerJoined();
+
+ NetworkGameInitData* param = new NetworkGameInitData();
+ param->seed = 0;
+ param->settings = app.GetGameHostOption(eGameHostOption_All);
+
+ g_NetworkManager.ServerStoppedCreate(true);
+ g_NetworkManager.ServerReadyCreate(true);
+
+ C4JThread* thread = new C4JThread(&CGameNetworkManager::ServerThreadProc, param, "Server", 256 * 1024);
+ thread->SetProcessor(CPU_CORE_SERVER);
+ thread->Run();
+
+ g_NetworkManager.ServerReadyWait();
+ g_NetworkManager.ServerReadyDestroy();
+
+ if (MinecraftServer::serverHalted())
+ {
+ fprintf(stderr, "The server halted during startup.\n");
+ g_NetworkManager.LeaveGame(false);
+ return 1;
+ }
+
+ app.SetGameStarted(true);
+ g_NetworkManager.DoWork();
+
+ printf("Server ready on %s:%d\n", bindIp, port);
+ printf("Type 'help' for server commands.\n");
+ fflush(stdout);
+
+ C4JThread* consoleThread = new C4JThread(&HeadlessServerConsoleThreadProc, NULL, "Server console", 128 * 1024);
+ consoleThread->Run();
+
+ MSG msg = { 0 };
+ while (WM_QUIT != msg.message && !app.m_bShutdown && !MinecraftServer::serverHalted())
+ {
+ if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ continue;
+ }
+
+ app.UpdateTime();
+ ProfileManager.Tick();
+ StorageManager.Tick();
+ RenderManager.Tick();
+ ui.tick();
+ g_NetworkManager.DoWork();
+ app.HandleXuiActions();
+
+ Sleep(10);
+ }
+
+ printf("Stopping server...\n");
+ fflush(stdout);
+
+ app.m_bShutdown = true;
+ MinecraftServer::HaltServer();
+ g_NetworkManager.LeaveGame(false);
+ return 0;
+}
+
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
@@ -742,70 +1106,8 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
SetProcessDPIAware();
g_iScreenWidth = GetSystemMetrics(SM_CXSCREEN);
g_iScreenHeight = GetSystemMetrics(SM_CYSCREEN);
-
- if(lpCmdLine)
- {
- if(lpCmdLine[0] == '1')
- {
- g_iScreenWidth = 1280;
- g_iScreenHeight = 720;
- }
- else if(lpCmdLine[0] == '2')
- {
- g_iScreenWidth = 640;
- g_iScreenHeight = 480;
- }
- else if(lpCmdLine[0] == '3')
- {
- // Vita
- g_iScreenWidth = 720;
- g_iScreenHeight = 408;
-
- // Vita native
- //g_iScreenWidth = 960;
- //g_iScreenHeight = 544;
- }
-
- char cmdLineA[1024];
- strncpy_s(cmdLineA, sizeof(cmdLineA), lpCmdLine, _TRUNCATE);
-
- char *nameArg = strstr(cmdLineA, "-name ");
- if (nameArg)
- {
- nameArg += 6;
- while (*nameArg == ' ') nameArg++;
- char nameBuf[17];
- int n = 0;
- while (nameArg[n] && nameArg[n] != ' ' && n < 16) { nameBuf[n] = nameArg[n]; n++; }
- nameBuf[n] = 0;
- strncpy_s(g_Win64Username, 17, nameBuf, _TRUNCATE);
- }
-
- char *ipArg = strstr(cmdLineA, "-ip ");
- if (ipArg)
- {
- ipArg += 4;
- while (*ipArg == ' ') ipArg++;
- char ipBuf[256];
- int n = 0;
- while (ipArg[n] && ipArg[n] != ' ' && n < 255) { ipBuf[n] = ipArg[n]; n++; }
- ipBuf[n] = 0;
- strncpy_s(g_Win64MultiplayerIP, 256, ipBuf, _TRUNCATE);
- g_Win64MultiplayerJoin = true;
- }
-
- char *portArg = strstr(cmdLineA, "-port ");
- if (portArg)
- {
- portArg += 6;
- while (*portArg == ' ') portArg++;
- char portBuf[16];
- int n = 0;
- while (portArg[n] && portArg[n] != ' ' && n < 15) { portBuf[n] = portArg[n]; n++; }
- portBuf[n] = 0;
- g_Win64MultiplayerPort = atoi(portBuf);
- }
- }
+ Win64LaunchOptions launchOptions = ParseLaunchOptions();
+ ApplyScreenMode(launchOptions.screenMode);
if (g_Win64Username[0] == 0)
{
@@ -821,7 +1123,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
MyRegisterClass(hInstance);
// Perform application initialization:
- if (!InitInstance (hInstance, nCmdShow))
+ if (!InitInstance (hInstance, launchOptions.serverMode ? SW_HIDE : nCmdShow))
{
return FALSE;
}
@@ -834,6 +1136,13 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
return 0;
}
+ if (launchOptions.serverMode)
+ {
+ int serverResult = RunHeadlessServer();
+ CleanupDevice();
+ return serverResult;
+ }
+
#if 0
// Main message loop
MSG msg = {0};
@@ -886,203 +1195,13 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
}
#endif
- app.loadMediaArchive();
-
- RenderManager.Initialise(g_pd3dDevice, g_pSwapChain);
-
- app.loadStringTable();
- ui.init(g_pd3dDevice,g_pImmediateContext,g_pRenderTargetView,g_pDepthStencilView,g_iScreenWidth,g_iScreenHeight);
-
- ////////////////
- // Initialise //
- ////////////////
-
- // Set the number of possible joypad layouts that the user can switch between, and the number of actions
- InputManager.Initialise(1,3,MINECRAFT_ACTION_MAX, ACTION_MAX_MENU);
-
- // Initialize keyboard/mouse input
- KMInput.Init(g_hWnd);
-
- // Set the default joypad action mappings for Minecraft
- DefineActions();
- InputManager.SetJoypadMapVal(0,0);
- InputManager.SetKeyRepeatRate(0.3f,0.2f);
-
- // Initialise the profile manager with the game Title ID, Offer ID, a profile version number, and the number of profile values and settings
- ProfileManager.Initialise(TITLEID_MINECRAFT,
- app.m_dwOfferID,
- PROFILE_VERSION_10,
- NUM_PROFILE_VALUES,
- NUM_PROFILE_SETTINGS,
- dwProfileSettingsA,
- app.GAME_DEFINED_PROFILE_DATA_BYTES*XUSER_MAX_COUNT,
- &app.uiGameDefinedDataChangedBitmask
- );
-#if 0
- // register the awards
- ProfileManager.RegisterAward(eAward_TakingInventory, ACHIEVEMENT_01, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_GettingWood, ACHIEVEMENT_02, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_Benchmarking, ACHIEVEMENT_03, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_TimeToMine, ACHIEVEMENT_04, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_HotTopic, ACHIEVEMENT_05, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_AquireHardware, ACHIEVEMENT_06, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_TimeToFarm, ACHIEVEMENT_07, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_BakeBread, ACHIEVEMENT_08, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_TheLie, ACHIEVEMENT_09, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_GettingAnUpgrade, ACHIEVEMENT_10, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_DeliciousFish, ACHIEVEMENT_11, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_OnARail, ACHIEVEMENT_12, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_TimeToStrike, ACHIEVEMENT_13, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_MonsterHunter, ACHIEVEMENT_14, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_CowTipper, ACHIEVEMENT_15, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_WhenPigsFly, ACHIEVEMENT_16, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_LeaderOfThePack, ACHIEVEMENT_17, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_MOARTools, ACHIEVEMENT_18, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_DispenseWithThis, ACHIEVEMENT_19, eAwardType_Achievement);
- ProfileManager.RegisterAward(eAward_InToTheNether, ACHIEVEMENT_20, eAwardType_Achievement);
-
- ProfileManager.RegisterAward(eAward_mine100Blocks, GAMER_PICTURE_GAMERPIC1, eAwardType_GamerPic,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_GAMERPIC1,IDS_CONFIRM_OK);
- ProfileManager.RegisterAward(eAward_kill10Creepers, GAMER_PICTURE_GAMERPIC2, eAwardType_GamerPic,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_GAMERPIC2,IDS_CONFIRM_OK);
-
- ProfileManager.RegisterAward(eAward_eatPorkChop, AVATARASSETAWARD_PORKCHOP_TSHIRT, eAwardType_AvatarItem,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_AVATAR1,IDS_CONFIRM_OK);
- ProfileManager.RegisterAward(eAward_play100Days, AVATARASSETAWARD_WATCH, eAwardType_AvatarItem,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_AVATAR2,IDS_CONFIRM_OK);
- ProfileManager.RegisterAward(eAward_arrowKillCreeper, AVATARASSETAWARD_CAP, eAwardType_AvatarItem,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_AVATAR3,IDS_CONFIRM_OK);
-
- ProfileManager.RegisterAward(eAward_socialPost, 0, eAwardType_Theme,false,app.GetStringTable(),IDS_AWARD_TITLE,IDS_AWARD_THEME,IDS_CONFIRM_OK,THEME_NAME,THEME_FILESIZE);
-
- // Rich Presence init - number of presences, number of contexts
- ProfileManager.RichPresenceInit(4,1);
- ProfileManager.RegisterRichPresenceContext(CONTEXT_GAME_STATE);
-
- // initialise the storage manager with a default save display name, a Minimum save size, and a callback for displaying the saving message
- StorageManager.Init(app.GetString(IDS_DEFAULT_SAVENAME),"savegame.dat",FIFTY_ONE_MB,&CConsoleMinecraftApp::DisplaySavingMessage,(LPVOID)&app);
- // Set up the global title storage path
- StorageManager.StoreTMSPathName();
-
- // set a function to be called when there's a sign in change, so we can exit a level if the primary player signs out
- ProfileManager.SetSignInChangeCallback(&CConsoleMinecraftApp::SignInChangeCallback,(LPVOID)&app);
-
- // set a function to be called when the ethernet is disconnected, so we can back out if required
- ProfileManager.SetNotificationsCallback(&CConsoleMinecraftApp::NotificationsCallback,(LPVOID)&app);
-
-#endif
- // Set a callback for the default player options to be set - when there is no profile data for the player
- ProfileManager.SetDefaultOptionsCallback(&CConsoleMinecraftApp::DefaultOptionsCallback,(LPVOID)&app);
-#if 0
- // Set a callback to deal with old profile versions needing updated to new versions
- ProfileManager.SetOldProfileVersionCallback(&CConsoleMinecraftApp::OldProfileVersionCallback,(LPVOID)&app);
-
- // Set a callback for when there is a read error on profile data
- ProfileManager.SetProfileReadErrorCallback(&CConsoleMinecraftApp::ProfileReadErrorCallback,(LPVOID)&app);
-
-#endif
- // QNet needs to be setup after profile manager, as we do not want its Notify listener to handle
- // XN_SYS_SIGNINCHANGED notifications. This does mean that we need to have a callback in the
- // ProfileManager for XN_LIVE_INVITE_ACCEPTED for QNet.
- g_NetworkManager.Initialise();
-
- for (int i = 0; i < MINECRAFT_NET_MAX_PLAYERS; i++)
+ Minecraft *pMinecraft = InitialiseMinecraftRuntime();
+ if (pMinecraft == NULL)
{
- IQNet::m_player[i].m_smallId = (BYTE)i;
- IQNet::m_player[i].m_isRemote = false;
- IQNet::m_player[i].m_isHostPlayer = (i == 0);
- swprintf_s(IQNet::m_player[i].m_gamertag, 32, L"Player%d", i);
- }
- extern wchar_t g_Win64UsernameW[17];
- wcscpy_s(IQNet::m_player[0].m_gamertag, 32, g_Win64UsernameW);
-
- WinsockNetLayer::Initialize();
-
- // 4J-PB moved further down
- //app.InitGameSettings();
-
- // debug switch to trial version
- ProfileManager.SetDebugFullOverride(true);
-
-#if 0
- //ProfileManager.AddDLC(2);
- StorageManager.SetDLCPackageRoot("DLCDrive");
- StorageManager.RegisterMarketplaceCountsCallback(&CConsoleMinecraftApp::MarketplaceCountsCallback,(LPVOID)&app);
- // Kinect !
-
- if(XNuiGetHardwareStatus()!=0)
- {
- // If the Kinect Sensor is not physically connected, this function returns 0.
- NuiInitialize(NUI_INITIALIZE_FLAG_USES_HIGH_QUALITY_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH |
- NUI_INITIALIZE_FLAG_EXTRAPOLATE_FLOOR_PLANE | NUI_INITIALIZE_FLAG_USES_FITNESS | NUI_INITIALIZE_FLAG_NUI_GUIDE_DISABLED | NUI_INITIALIZE_FLAG_SUPPRESS_AUTOMATIC_UI,NUI_INITIALIZE_DEFAULT_HARDWARE_THREAD );
+ CleanupDevice();
+ return 1;
}
- // Sentient !
- hr = TelemetryManager->Init();
-
-#endif
- // Initialise TLS for tesselator, for this main thread
- Tesselator::CreateNewThreadStorage(1024*1024);
- // Initialise TLS for AABB and Vec3 pools, for this main thread
- AABB::CreateNewThreadStorage();
- Vec3::CreateNewThreadStorage();
- IntCache::CreateNewThreadStorage();
- Compression::CreateNewThreadStorage();
- OldChunkStorage::CreateNewThreadStorage();
- Level::enableLightingCache();
- Tile::CreateNewThreadStorage();
-
- Minecraft::main();
- Minecraft *pMinecraft=Minecraft::GetInstance();
-
- app.InitGameSettings();
-
-#if 0
- //bool bDisplayPauseMenu=false;
-
- // set the default gamma level
- float fVal=50.0f*327.68f;
- RenderManager.UpdateGamma((unsigned short)fVal);
-
- // load any skins
- //app.AddSkinsToMemoryTextureFiles();
-
- // set the achievement text for a trial achievement, now we have the string table loaded
- ProfileManager.SetTrialTextStringTable(app.GetStringTable(),IDS_CONFIRM_OK, IDS_CONFIRM_CANCEL);
- ProfileManager.SetTrialAwardText(eAwardType_Achievement,IDS_UNLOCK_TITLE,IDS_UNLOCK_ACHIEVEMENT_TEXT);
- ProfileManager.SetTrialAwardText(eAwardType_GamerPic,IDS_UNLOCK_TITLE,IDS_UNLOCK_GAMERPIC_TEXT);
- ProfileManager.SetTrialAwardText(eAwardType_AvatarItem,IDS_UNLOCK_TITLE,IDS_UNLOCK_AVATAR_TEXT);
- ProfileManager.SetTrialAwardText(eAwardType_Theme,IDS_UNLOCK_TITLE,IDS_UNLOCK_THEME_TEXT);
- ProfileManager.SetUpsellCallback(&app.UpsellReturnedCallback,&app);
-
- // Set up a debug character press sequence
-#ifndef _FINAL_BUILD
- app.SetDebugSequence("LRLRYYY");
-#endif
-
- // Initialise the social networking manager.
- CSocialManager::Instance()->Initialise();
-
- // Update the base scene quick selects now that the minecraft class exists
- //CXuiSceneBase::UpdateScreenSettings(0);
-#endif
- app.InitialiseTips();
-#if 0
-
- DWORD initData=0;
-
-#ifndef _FINAL_BUILD
-#ifndef _DEBUG
-#pragma message(__LOC__"Need to define the _FINAL_BUILD before submission")
-#endif
-#endif
-
- // Set the default sound levels
- pMinecraft->options->set(Options::Option::MUSIC,1.0f);
- pMinecraft->options->set(Options::Option::SOUND,1.0f);
-
- app.NavigateToScene(XUSER_INDEX_ANY,eUIScene_Intro,&initData);
-#endif
-
- // Set the default sound levels
- pMinecraft->options->set(Options::Option::MUSIC,1.0f);
- pMinecraft->options->set(Options::Option::SOUND,1.0f);
-
//app.TemporaryCreateGameStart();
//Sleep(10000);
@@ -1637,4 +1756,4 @@ void MemPixStuff()
PIXAddNamedCounter(((float)allSectsTotal)/(4096.0f),"MemSect total pages");
}
-#endif \ No newline at end of file
+#endif
diff --git a/Minecraft.World/Minecraft.World.vcxproj b/Minecraft.World/Minecraft.World.vcxproj
index 58880529..7ab7c4ce 100644
--- a/Minecraft.World/Minecraft.World.vcxproj
+++ b/Minecraft.World/Minecraft.World.vcxproj
@@ -1257,6 +1257,7 @@
<UseFullPaths>false</UseFullPaths>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
+ <AdditionalOptions>/FS %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -1355,7 +1356,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
<StringPooling>true</StringPooling>
- <AdditionalOptions>/Ob3</AdditionalOptions>
+ <AdditionalOptions>/FS /Ob3 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
@@ -4957,4 +4958,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
-</Project> \ No newline at end of file
+</Project>
diff --git a/README.md b/README.md
index e6583e76..a84ee508 100644
--- a/README.md
+++ b/README.md
@@ -41,14 +41,22 @@ This feature is based on [LCEMP](https://github.com/LCEMP/LCEMP/)
| Argument | Description |
|--------------------|----------------------------------------------------------------------------------------------------------------|
| `-name <username>` | Sets your in-game username |
-| `-ip <address>` | Manually connect to an IP if LAN advertising does not work or if the server cannot be discovered automatically |
-| `-port <port>` | Override the default port if it was changed in the source |
+| `-server` | Launches a headless server instead of the client |
+| `-ip <address>` | Client mode: manually connect to an IP. Server mode: override the bind IP from `server.properties` |
+| `-port <port>` | Client mode: override the join port. Server mode: override the listen port from `server.properties` |
Example:
```
Minecraft.Client.exe -name Steve -ip 192.168.0.25 -port 25565
```
+Headless server example:
+```
+Minecraft.Client.exe -server -ip 0.0.0.0 -port 25565
+```
+
+The headless server also reads and writes `server.properties` in the working directory. If `-ip` / `-port` are omitted in `-server` mode, it falls back to `server-ip` / `server-port` from that file. Dedicated-server host options such as `trust-players`, `pvp`, `fire-spreads`, `tnt`, `difficulty`, `gamemode`, `spawn-animals`, and `spawn-npcs` are persisted there as well.
+
## Controls (Keyboard & Mouse)
- **Movement**: `W` `A` `S` `D`